Skip to content

Commit

Permalink
[GR-58468] Load lambda types in the application layer
Browse files Browse the repository at this point in the history
PullRequest: graal/18883
  • Loading branch information
Zeavee committed Nov 8, 2024
2 parents e002c8a + 039ff63 commit 806c89a
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 CAPTURING_CLASS_TAG = "capturing 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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method)
private final ConcurrentMap<BootstrapMethodRecord, BootstrapMethodInfo> bootstrapMethodInfoCache = new ConcurrentHashMap<>();
private final Set<Executable> indyBuildTimeAllowList;
private final Set<Executable> condyBuildTimeAllowList;
private final Method metafactory;
private final Method altMetafactory;

public static BootstrapMethodConfiguration singleton() {
return ImageSingletons.lookup(BootstrapMethodConfiguration.class);
Expand All @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -208,10 +209,15 @@ protected void prepareConstantRelinking(EconomicMap<String, Object> constantData
@Override
protected boolean delegateProcessing(String constantType, Object constantValue, List<Object> 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<JavaConstant> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,45 @@
package com.oracle.svm.hosted.heap;

import static com.oracle.graal.pointsto.heap.ImageLayerLoader.get;
import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAPTURING_CLASS_TAG;
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.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;
import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.THROW_ALLOCATED_OBJECT_TAG;
import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_METHOD_TAG;
import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.WRAPPED_TYPE_TAG;
import static com.oracle.svm.hosted.lambda.LambdaParser.createMethodGraph;
import static com.oracle.svm.hosted.lambda.LambdaParser.getLambdaClassFromConstantNode;

import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.graalvm.collections.EconomicMap;

import com.oracle.graal.pointsto.BigBang;
import com.oracle.graal.pointsto.heap.ImageLayerLoader;
import com.oracle.graal.pointsto.heap.ImageLayerLoaderHelper;
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.reflect.serialize.SerializationSupport;
import com.oracle.svm.hosted.code.FactoryMethodSupport;
import com.oracle.svm.hosted.lambda.LambdaParser;
import com.oracle.svm.hosted.reflect.serialize.SerializationFeature;
import com.oracle.svm.util.ReflectionUtil;

import jdk.graal.compiler.graph.iterators.NodeIterable;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.options.OptionValues;
import jdk.internal.reflect.ReflectionFactory;
import jdk.vm.ci.meta.ResolvedJavaMethod;

public class SVMImageLayerLoaderHelper extends ImageLayerLoaderHelper {
private final Map<Class<?>, Boolean> capturingClasses = new ConcurrentHashMap<>();

public SVMImageLayerLoaderHelper(ImageLayerLoader imageLayerLoader) {
super(imageLayerLoader);
}
Expand All @@ -70,11 +86,45 @@ protected boolean loadType(EconomicMap<String, Object> typeData, int tid) {
Class<?> constructorAccessor = serializationSupport.getSerializationConstructorAccessor(rawDeclaringClass, rawTargetConstructorClass).getClass();
imageLayerLoader.getMetaAccess().lookupJavaType(constructorAccessor);
return true;
} else if (wrappedType.equals(LAMBDA_TYPE_TAG)) {
String capturingClassName = get(typeData, CAPTURING_CLASS_TAG);
Class<?> capturingClass = imageLayerLoader.lookupClass(false, capturingClassName);
loadLambdaTypes(capturingClass);
return true;
}

return super.loadType(typeData, tid);
}

/**
* Load all lambda types of the given capturing class. Each method of the capturing class is
* parsed (see {@link LambdaParser#createMethodGraph(ResolvedJavaMethod, OptionValues)}). The
* lambda types can then be found in the constant nodes of the graphs.
*/
private void loadLambdaTypes(Class<?> capturingClass) {
AnalysisUniverse universe = imageLayerLoader.getUniverse();
capturingClasses.computeIfAbsent(capturingClass, key -> {
LambdaParser.allExecutablesDeclaredInClass(universe.getOriginalMetaAccess().lookupJavaType(capturingClass))
.filter(m -> m.getCode() != null)
.forEach(m -> loadLambdaTypes(m, universe.getBigbang()));
return true;
});
}

private static void loadLambdaTypes(ResolvedJavaMethod m, BigBang bigBang) {
StructuredGraph graph = createMethodGraph(m, bigBang.getOptions());

NodeIterable<ConstantNode> constantNodes = ConstantNode.getConstantNodes(graph);

for (ConstantNode cNode : constantNodes) {
Class<?> lambdaClass = getLambdaClassFromConstantNode(cNode);

if (lambdaClass != null) {
bigBang.getMetaAccess().lookupJavaType(lambdaClass);
}
}
}

@Override
protected boolean loadMethod(EconomicMap<String, Object> methodData, int mid) {
String wrappedMethod = get(methodData, WRAPPED_METHOD_TAG);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
*/
package com.oracle.svm.hosted.heap;

import static com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil.CAPTURING_CLASS_TAG;
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.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;
Expand All @@ -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);
Expand All @@ -55,6 +59,9 @@ protected void persistType(AnalysisType type, EconomicMap<String, Object> 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(CAPTURING_CLASS_TAG, LambdaUtils.capturingClass(type.toJavaName()));
}
super.persistType(type, typeMap);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2024, 2024, 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.hosted.lambda;

import java.lang.reflect.Member;
import java.util.Arrays;
import java.util.stream.Stream;

import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin;
import com.oracle.graal.pointsto.util.GraalAccess;
import com.oracle.svm.util.ClassUtil;

import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.java.BytecodeParser;
import jdk.graal.compiler.java.GraphBuilderPhase;
import jdk.graal.compiler.java.LambdaUtils;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext;
import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins;
import jdk.graal.compiler.nodes.spi.CoreProviders;
import jdk.graal.compiler.options.OptionValues;
import jdk.graal.compiler.phases.OptimisticOptimizations;
import jdk.graal.compiler.phases.tiers.HighTierContext;
import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
import jdk.graal.compiler.replacements.MethodHandlePlugin;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;

public class LambdaParser {
/**
* Create a {@link StructuredGraph} using {@link LambdaGraphBuilderPhase.LambdaBytecodeParser},
* a simple {@link BytecodeParser}.
*/
@SuppressWarnings("try")
public static StructuredGraph createMethodGraph(ResolvedJavaMethod method, OptionValues options) {
GraphBuilderPhase lambdaParserPhase = new LambdaParser.LambdaGraphBuilderPhase();
DebugContext.Description description = new DebugContext.Description(method, ClassUtil.getUnqualifiedName(method.getClass()) + ":" + method.getName());
DebugContext debug = new DebugContext.Builder(options, new GraalDebugHandlersFactory(GraalAccess.getOriginalSnippetReflection())).description(description).build();

HighTierContext context = new HighTierContext(GraalAccess.getOriginalProviders(), null, OptimisticOptimizations.NONE);
StructuredGraph graph = new StructuredGraph.Builder(debug.getOptions(), debug)
.method(method)
.recordInlinedMethods(false)
.build();
try (DebugContext.Scope ignored = debug.scope("ParsingToMaterializeLambdas")) {
lambdaParserPhase.apply(graph, context);
} catch (Throwable e) {
throw debug.handle(e);
}
return graph;
}

public static Stream<? extends ResolvedJavaMethod> allExecutablesDeclaredInClass(ResolvedJavaType t) {
return Stream.concat(Stream.concat(
Arrays.stream(t.getDeclaredMethods(false)),
Arrays.stream(t.getDeclaredConstructors(false))),
t.getClassInitializer() == null ? Stream.empty() : Stream.of(t.getClassInitializer()));
}

/**
* Get the lambda class in the constant if it is a {@code DirectMethodHandle}, by getting the
* declaring class of the {@code member} field.
*/
public static Class<?> getLambdaClassFromConstantNode(ConstantNode constantNode) {
Constant constant = constantNode.getValue();
Class<?> lambdaClass = getLambdaClassFromMemberField(constant);

if (lambdaClass == null) {
return null;
}

return LambdaUtils.isLambdaClass(lambdaClass) ? lambdaClass : null;
}

private static Class<?> getLambdaClassFromMemberField(Constant constant) {
ResolvedJavaType constantType = GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType((JavaConstant) constant);

if (constantType == null) {
return null;
}

ResolvedJavaField[] fields = constantType.getInstanceFields(true);
ResolvedJavaField targetField = null;
for (ResolvedJavaField field : fields) {
if (field.getName().equals("member")) {
targetField = field;
break;
}
}

if (targetField == null) {
return null;
}

JavaConstant fieldValue = GraalAccess.getOriginalProviders().getConstantReflection().readFieldValue(targetField, (JavaConstant) constant);
Member memberField = GraalAccess.getOriginalProviders().getSnippetReflection().asObject(Member.class, fieldValue);
return memberField.getDeclaringClass();
}

static class LambdaGraphBuilderPhase extends GraphBuilderPhase {
LambdaGraphBuilderPhase() {
super(buildLambdaParserConfig());
}

LambdaGraphBuilderPhase(GraphBuilderConfiguration config) {
super(config);
}

private static GraphBuilderConfiguration buildLambdaParserConfig() {
GraphBuilderConfiguration.Plugins plugins = new GraphBuilderConfiguration.Plugins(new InvocationPlugins());
plugins.setClassInitializationPlugin(new NoClassInitializationPlugin());
plugins.prependNodePlugin(new MethodHandlePlugin(GraalAccess.getOriginalProviders().getConstantReflection().getMethodHandleAccess(), false));
return GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
}

@Override
public GraphBuilderPhase copyWithConfig(GraphBuilderConfiguration config) {
return new LambdaGraphBuilderPhase(config);
}

static class LambdaBytecodeParser extends BytecodeParser {
protected LambdaBytecodeParser(Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) {
super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext);
}
}

@Override
protected Instance createInstance(CoreProviders providers, GraphBuilderConfiguration instanceGBConfig, OptimisticOptimizations optimisticOpts, IntrinsicContext initialIntrinsicContext) {
return new Instance(providers, instanceGBConfig, optimisticOpts, initialIntrinsicContext) {
@Override
protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) {
return new LambdaBytecodeParser(this, graph, parent, method, entryBCI, intrinsicContext);
}
};
}
}
}
Loading

0 comments on commit 806c89a

Please sign in to comment.