diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java index 3d30d926ac03..2dac7c683aba 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java @@ -105,6 +105,8 @@ public class ImageLayerSnapshotUtil { public static final String INTERFACES_TAG = "interfaces"; public static final String WRAPPED_TYPE_TAG = "wrapped type"; public static final String GENERATED_SERIALIZATION_TAG = "generated serialization"; + public static final String LAMBDA_TYPE_TAG = "lambda type"; + public static final String HOLDER_CLASS_TAG = "holder class"; public static final String RAW_DECLARING_CLASS_TAG = "raw declaring class"; public static final String RAW_TARGET_CONSTRUCTOR_CLASS_TAG = "raw target constructor class"; public static final String CONSTANTS_TAG = "constants"; 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 index 616c2ef2e142..5b0bbeab806c 100644 --- 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 @@ -69,6 +69,8 @@ public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) 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); @@ -79,10 +81,10 @@ public BootstrapMethodConfiguration() { * Bootstrap method used for Lambdas. Executing this method at run time implies defining * hidden class at run time, which is unsupported. */ - Method metafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, + metafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class); /* Alternate version of LambdaMetafactory.metafactory. */ - Method altMetafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); + 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 @@ -143,6 +145,10 @@ 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. */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java index 32d964b48e63..798be2c62779 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java @@ -64,6 +64,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; @@ -235,10 +236,15 @@ protected void prepareConstantRelinking(EconomicMap constantData @Override protected boolean delegateProcessing(String constantType, Object constantValue, List constantData, Object[] values, int i) { if (constantType.equals(METHOD_POINTER_TAG)) { - AnalysisType methodPointerType = metaAccess.lookupJavaType(MethodPointer.class); - int mid = (int) constantValue; - AnalysisMethod method = getAnalysisMethod(mid); - values[i] = new RelocatableConstant(new MethodPointer(method), methodPointerType); + AnalysisFuture task = new AnalysisFuture<>(() -> { + AnalysisType methodPointerType = metaAccess.lookupJavaType(MethodPointer.class); + int mid = (int) constantValue; + AnalysisMethod method = getAnalysisMethod(mid); + RelocatableConstant constant = new RelocatableConstant(new MethodPointer(method), methodPointerType); + values[i] = constant; + return constant; + }); + values[i] = task; return true; } else if (constantType.equals(C_ENTRY_POINT_LITERAL_CODE_POINTER)) { AnalysisType cEntryPointerLiteralPointerType = metaAccess.lookupJavaType(CEntryPointLiteralCodePointer.class); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java index 5d0b6b9628ba..96f90861edad 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoaderHelper.java @@ -27,6 +27,8 @@ import static com.oracle.graal.pointsto.heap.ImageLayerLoader.get; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.FACTORY_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.GENERATED_SERIALIZATION_TAG; +import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.HOLDER_CLASS_TAG; +import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LAMBDA_TYPE_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_DECLARING_CLASS_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.RAW_TARGET_CONSTRUCTOR_CLASS_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.TARGET_CONSTRUCTOR_TAG; @@ -35,19 +37,42 @@ import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_TYPE_TAG; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.AnnotationAccess; import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.heap.ImageLayerLoaderHelper; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; import com.oracle.svm.core.reflect.serialize.SerializationSupport; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.code.FactoryMethodSupport; import com.oracle.svm.hosted.reflect.serialize.SerializationFeature; +import com.oracle.svm.hosted.substitute.SubstitutionMethod; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.internal.reflect.ReflectionFactory; +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.JavaConstant; public class SVMImageLayerLoaderHelper extends ImageLayerLoaderHelper { + private static final Class DIRECT_METHOD_HANDLE_STATIC_ACCESSOR_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.DirectMethodHandle$StaticAccessor"); + private static final Class DIRECT_METHOD_HANDLE_CONSTRUCTOR_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.DirectMethodHandle$Constructor"); + private static final String STATIC_BASE_FIELD_NAME = "staticBase"; + private static final String INSTANCE_CLASS_FIELD_NAME = "instanceClass"; + private static final int INVOKE_DYNAMIC_OPCODE = 186; + public SVMImageLayerLoaderHelper(ImageLayerLoader imageLayerLoader) { super(imageLayerLoader); } @@ -70,11 +95,77 @@ protected boolean loadType(EconomicMap typeData, int tid) { Class constructorAccessor = serializationSupport.getSerializationConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass).getClass(); imageLayerLoader.getMetaAccess().lookupJavaType(constructorAccessor); return true; + } else if (wrappedType.equals(LAMBDA_TYPE_TAG)) { + String holderClassName = get(typeData, HOLDER_CLASS_TAG); + Class holderClass = imageLayerLoader.lookupClass(false, holderClassName); + loadLambdaTypes(holderClass); + return true; } return super.loadType(typeData, tid); } + /** + * The constant pool index of bootstrap method is not stable in different JVM instances, so the + * only solution is to load all lambda types of the given holder class. + */ + private void loadLambdaTypes(Class holderClass) { + AnalysisUniverse universe = imageLayerLoader.getUniverse(); + AnalysisType type = universe.getBigbang().getMetaAccess().lookupJavaType(holderClass); + boolean isSubstitution = AnnotationAccess.isAnnotationPresent(holderClass, TargetClass.class); + ConstantPool constantPool = getConstantPool(type, isSubstitution); + int index = JavaVersionUtil.JAVA_SPEC > 21 ? 0 : -1; + ConstantPool.BootstrapMethodInvocation bootstrap; + while ((bootstrap = getBootstrap(constantPool, index)) != null) { + if (BootstrapMethodConfiguration.singleton().isMetafactory(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod()))) { + constantPool.loadReferencedType(index, INVOKE_DYNAMIC_OPCODE); + JavaConstant test = constantPool.lookupAppendix(index, INVOKE_DYNAMIC_OPCODE); + Object appendix = universe.getSnippetReflection().asObject(Object.class, test); + + Class potentialLambdaClass; + if (DIRECT_METHOD_HANDLE_STATIC_ACCESSOR_CLASS.isInstance(appendix)) { + potentialLambdaClass = ReflectionUtil.readField(DIRECT_METHOD_HANDLE_STATIC_ACCESSOR_CLASS, STATIC_BASE_FIELD_NAME, appendix); + } else if (DIRECT_METHOD_HANDLE_CONSTRUCTOR_CLASS.isInstance(appendix)) { + potentialLambdaClass = ReflectionUtil.readField(DIRECT_METHOD_HANDLE_CONSTRUCTOR_CLASS, INSTANCE_CLASS_FIELD_NAME, appendix); + } else { + throw VMError.shouldNotReachHere("Unexpected appendix %s", appendix); + } + universe.getBigbang().getMetaAccess().lookupJavaType(potentialLambdaClass); + } + if (JavaVersionUtil.JAVA_SPEC > 21) { + index++; + } else { + index--; + } + } + } + + /** + * A default and substitution class have two different constant pools. The constant pool can + * only be fetched through the methods of the class, so we iterate over the methods and the + * constructors and take the first constant pool that matches the current class. + */ + private static ConstantPool getConstantPool(AnalysisType type, boolean isSubstitution) { + Stream candidates = Stream.concat(Arrays.stream(type.getDeclaredMethods(false)), Arrays.stream(type.getDeclaredConstructors(false))); + Optional cp = candidates.map(method -> { + Executable javaMethod = method.getJavaMethod(); + if (((javaMethod != null && AnnotationAccess.isAnnotationPresent(javaMethod.getDeclaringClass(), TargetClass.class)) || (method.wrapped instanceof SubstitutionMethod)) == isSubstitution) { + return method.getConstantPool(); + } + return null; + }).filter(Objects::nonNull).findAny(); + assert cp.isPresent() : String.format("No constant pool was found in the %s class.", isSubstitution ? "substitution" : "default"); + return cp.get(); + } + + private static ConstantPool.BootstrapMethodInvocation getBootstrap(ConstantPool constantPool, int index) { + try { + return constantPool.lookupBootstrapMethodInvocation(index, INVOKE_DYNAMIC_OPCODE); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + @Override protected boolean loadMethod(EconomicMap methodData, int mid) { String wrappedMethod = get(methodData, WRAPPED_METHOD_TAG); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java index a829d10a6a27..a649c9e71596 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java @@ -87,7 +87,6 @@ import jdk.graal.compiler.nodes.EncodedGraph; import jdk.graal.compiler.util.ObjectCopier; import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaMethod; public class SVMImageLayerSnapshotUtil extends ImageLayerSnapshotUtil { public static final String GENERATED_SERIALIZATION = "jdk.internal.reflect.GeneratedSerializationConstructorAccessor"; @@ -169,7 +168,7 @@ public String getMethodIdentifier(AnalysisMethod method) { return getGeneratedSerializationName(declaringClass) + ":" + method.getName(); } if (method.wrapped instanceof FactoryMethod factoryMethod) { - ResolvedJavaMethod targetConstructor = factoryMethod.getTargetConstructor(); + AnalysisMethod targetConstructor = method.getUniverse().lookup(factoryMethod.getTargetConstructor()); return addModuleName(targetConstructor.getDeclaringClass().toJavaName(true) + getQualifiedName(method), moduleName); } if (method.wrapped instanceof IncompatibleClassChangeFallbackMethod) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java index d9ebd7c99d98..bbe1782ef099 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java @@ -26,6 +26,7 @@ import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CLASS_ID_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.C_ENTRY_POINT_LITERAL_CODE_POINTER; +import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.HOLDER_CLASS_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.HUB_IDENTITY_HASH_CODE_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_KEYS; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IMAGE_SINGLETON_OBJECTS; @@ -40,11 +41,13 @@ import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_AT_BUILD_TIME_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_INITIALIZED_NO_TRACKING_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.IS_NO_INITIALIZER_NO_TRACKING_TAG; +import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LAMBDA_TYPE_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.LOCATION_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.METHOD_POINTER_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.OBJECT_OFFSET_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_OBJECT_FIELDS_TAG; import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.STATIC_PRIMITIVE_FIELDS_TAG; +import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_TYPE_TAG; import java.util.ArrayList; import java.util.Arrays; @@ -92,6 +95,7 @@ import com.oracle.svm.hosted.reflect.proxy.ProxySubstitutionType; import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.java.LambdaUtils; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -149,6 +153,11 @@ protected void persistType(AnalysisType type, EconomicMap typeMa } } + if (LambdaUtils.isLambdaType(type)) { + typeMap.put(WRAPPED_TYPE_TAG, LAMBDA_TYPE_TAG); + typeMap.put(HOLDER_CLASS_TAG, LambdaUtils.capturingClass(type.toJavaName())); + } + super.persistType(type, typeMap); }