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 cf2eedc7a0ff3..cbef8f647b636 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 @@ -102,6 +102,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 616c2ef2e142a..5b0bbeab806c6 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 5071af10b2855..18634bd58e353 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 @@ -63,6 +63,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; @@ -208,10 +209,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 5d0b6b9628ba0..f856161c7ef5f 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,80 @@ 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, tid); + 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, int tid) { + 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 appendixConstant = constantPool.lookupAppendix(index, INVOKE_DYNAMIC_OPCODE); + Object appendix = universe.getSnippetReflection().asObject(Object.class, appendixConstant); + + 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); + } + AnalysisType lambda = universe.getBigbang().getMetaAccess().lookupJavaType(potentialLambdaClass); + if (lambda.getId() == tid) { + return; + } + } + 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 3e8e8466f8ad7..5b395f31dc667 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 @@ -75,7 +75,6 @@ import jdk.graal.compiler.util.ObjectCopierInputStream; import jdk.graal.compiler.util.ObjectCopierOutputStream; 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"; @@ -174,7 +173,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/SVMImageLayerWriterHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java index 5168c4d762fe3..2b93b2fb52486 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriterHelper.java @@ -26,6 +26,8 @@ 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; @@ -43,6 +45,8 @@ import com.oracle.svm.core.reflect.serialize.SerializationSupport; import com.oracle.svm.hosted.code.FactoryMethod; +import jdk.graal.compiler.java.LambdaUtils; + public class SVMImageLayerWriterHelper extends ImageLayerWriterHelper { public SVMImageLayerWriterHelper(ImageLayerWriter imageLayerWriter) { super(imageLayerWriter); @@ -55,6 +59,9 @@ protected void persistType(AnalysisType type, EconomicMap typeMa var key = SerializationSupport.singleton().getKeyFromConstructorAccessorClass(type.getJavaClass()); typeMap.put(RAW_DECLARING_CLASS_TAG, key.getDeclaringClass().getName()); typeMap.put(RAW_TARGET_CONSTRUCTOR_CLASS_TAG, key.getTargetConstructorClass().getName()); + } else 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); }