Skip to content

Commit

Permalink
[GR-49383] Implement base layer open-world analysis
Browse files Browse the repository at this point in the history
PullRequest: graal/15778
  • Loading branch information
Zeavee committed Jan 9, 2024
2 parents e026ca2 + 5ab9db9 commit 6df04c9
Show file tree
Hide file tree
Showing 22 changed files with 474 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,11 @@
import java.util.ArrayList;
import java.util.List;

import jdk.graal.compiler.api.replacements.SnippetReflectionProvider;
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.debug.Indent;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins;
import jdk.graal.compiler.options.OptionValues;
import jdk.graal.compiler.phases.util.Providers;
import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
import jdk.graal.compiler.word.WordTypes;
import org.graalvm.nativeimage.hosted.Feature;

import com.oracle.graal.pointsto.AnalysisObjectScanningObserver;
import com.oracle.graal.pointsto.AnalysisPolicy;
import com.oracle.graal.pointsto.ClassInclusionPolicy;
import com.oracle.graal.pointsto.api.PointstoOptions;
import com.oracle.graal.pointsto.flow.context.bytecode.BytecodeSensitiveAnalysisPolicy;
import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier;
Expand Down Expand Up @@ -79,6 +71,15 @@
import com.oracle.svm.util.ModuleSupport;
import com.oracle.svm.util.ReflectionUtil;

import jdk.graal.compiler.api.replacements.SnippetReflectionProvider;
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.debug.Indent;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins;
import jdk.graal.compiler.options.OptionValues;
import jdk.graal.compiler.phases.util.Providers;
import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
import jdk.graal.compiler.word.WordTypes;
import jdk.vm.ci.amd64.AMD64Kind;
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
import jdk.vm.ci.meta.JavaKind;
Expand Down Expand Up @@ -151,8 +152,9 @@ private PointsToAnalyzer(String mainEntryClass, OptionValues options) {
originalProviders.getPlatformConfigurationProvider(), aMetaAccessExtensionProvider, originalProviders.getLoopsDataProvider());
standaloneHost.initializeProviders(aProviders);
analysisName = getAnalysisName(mainEntryClass);
ClassInclusionPolicy classInclusionPolicy = new ClassInclusionPolicy.DefaultAllInclusionPolicy("Included in the base image");
bigbang = new StandalonePointsToAnalysis(options, aUniverse, standaloneHost, aMetaAccess, snippetReflection, aConstantReflection, aProviders.getWordTypes(), debugContext,
new TimerCollection());
new TimerCollection(), classInclusionPolicy);
standaloneHost.setImageName(analysisName);
aUniverse.setBigBang(bigbang);
ImageHeap heap = new ImageHeap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.oracle.graal.pointsto.ClassInclusionPolicy;
import com.oracle.graal.pointsto.PointsToAnalysis;
import com.oracle.graal.pointsto.api.HostVM;
import com.oracle.graal.pointsto.constraints.UnsupportedFeatures;
Expand All @@ -48,8 +49,8 @@ public class StandalonePointsToAnalysis extends PointsToAnalysis {
private final Set<AnalysisMethod> addedClinits = ConcurrentHashMap.newKeySet();

public StandalonePointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostVM hostVM, AnalysisMetaAccess metaAccess, SnippetReflectionProvider snippetReflectionProvider,
ConstantReflectionProvider constantReflectionProvider, WordTypes wordTypes, DebugContext debugContext, TimerCollection timerCollection) {
super(options, universe, hostVM, metaAccess, snippetReflectionProvider, constantReflectionProvider, wordTypes, new UnsupportedFeatures(), debugContext, timerCollection);
ConstantReflectionProvider constantReflectionProvider, WordTypes wordTypes, DebugContext debugContext, TimerCollection timerCollection, ClassInclusionPolicy classInclusionPolicy) {
super(options, universe, hostVM, metaAccess, snippetReflectionProvider, constantReflectionProvider, wordTypes, new UnsupportedFeatures(), debugContext, timerCollection, classInclusionPolicy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
package com.oracle.graal.pointsto;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

import org.graalvm.nativeimage.hosted.Feature;

import com.oracle.graal.pointsto.ClassInclusionPolicy.LayeredBaseImageInclusionPolicy;
import com.oracle.graal.pointsto.api.HostVM;
import com.oracle.graal.pointsto.api.PointstoOptions;
import com.oracle.graal.pointsto.constraints.UnsupportedFeatures;
Expand Down Expand Up @@ -97,11 +100,12 @@ public abstract class AbstractAnalysisEngine implements BigBang {
protected final Timer processFeaturesTimer;
protected final Timer analysisTimer;
protected final Timer verifyHeapTimer;
protected final ClassInclusionPolicy classInclusionPolicy;

@SuppressWarnings("this-escape")
public AbstractAnalysisEngine(OptionValues options, AnalysisUniverse universe, HostVM hostVM, AnalysisMetaAccess metaAccess, SnippetReflectionProvider snippetReflectionProvider,
ConstantReflectionProvider constantReflectionProvider, WordTypes wordTypes, UnsupportedFeatures unsupportedFeatures, DebugContext debugContext,
TimerCollection timerCollection) {
TimerCollection timerCollection, ClassInclusionPolicy classInclusionPolicy) {
this.options = options;
this.universe = universe;
this.debugHandlerFactories = Collections.singletonList(new GraalDebugHandlersFactory(snippetReflectionProvider));
Expand All @@ -123,6 +127,8 @@ public AbstractAnalysisEngine(OptionValues options, AnalysisUniverse universe, H
this.snippetReflectionProvider = snippetReflectionProvider;
this.constantReflectionProvider = constantReflectionProvider;
this.wordTypes = wordTypes;
classInclusionPolicy.setBigBang(this);
this.classInclusionPolicy = classInclusionPolicy;
}

/**
Expand Down Expand Up @@ -257,6 +263,10 @@ public void profileConstantObject(AnalysisType type) {
}
}

public boolean isBaseLayerAnalysisEnabled() {
return classInclusionPolicy instanceof LayeredBaseImageInclusionPolicy;
}

@Override
public OptionValues getOptions() {
return options;
Expand Down Expand Up @@ -346,6 +356,19 @@ public final boolean executorIsStarted() {
return executor.isStarted();
}

@Override
public void registerTypeForBaseImage(Class<?> cls) {
if (classInclusionPolicy.isClassIncluded(cls)) {
classInclusionPolicy.includeClass(cls);
Stream.concat(Arrays.stream(cls.getDeclaredConstructors()), Arrays.stream(cls.getDeclaredMethods()))
.filter(classInclusionPolicy::isMethodIncluded)
.forEach(classInclusionPolicy::includeMethod);
Arrays.stream(cls.getDeclaredFields())
.filter(classInclusionPolicy::isFieldIncluded)
.forEach(classInclusionPolicy::includeField);
}
}

/**
* Provide a non-null position. Some flows like newInstance and invoke require a non-null
* position, for others is just better. The constructed position is best-effort, i.e., it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,8 @@ default void afterAnalysis() {
default AnalysisMethod fallbackResolveConcreteMethod(AnalysisType resolvingType, AnalysisMethod method) {
return null;
}

default void registerTypeForBaseImage(Class<?> cls) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* 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.graal.pointsto;

import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;

import org.graalvm.nativeimage.AnnotationAccess;

import com.oracle.graal.pointsto.meta.AnalysisMethod;

import jdk.graal.compiler.api.replacements.Fold;

/**
* Policy used to determine which classes, methods and fields need to be included in the image when
* the {@code IncludeAllFromPath} and/or {@code IncludeAllFromModule} options are specified
* depending on the configuration.
*/
public abstract class ClassInclusionPolicy {
protected BigBang bb;
protected final Object reason;

public ClassInclusionPolicy(Object reason) {
this.reason = reason;
}

public void setBigBang(BigBang bb) {
this.bb = bb;
}

/**
* Determine if the given class needs to be included in the image according to the policy.
*/
public abstract boolean isClassIncluded(Class<?> cls);

/**
* Determine if the given method needs to be included in the image according to the policy.
*/
public boolean isMethodIncluded(Executable method) {
/*
* Methods annotated with @Fold should not be included in the base image as they must be
* inlined. An extension image would inline the method as well and would not use the method
* from the base image.
*/
return !AnnotationAccess.isAnnotationPresent(bb.getMetaAccess().lookupJavaMethod(method), Fold.class);
}

/**
* Determine if the given field needs to be included in the image according to the policy.
*/
public boolean isFieldIncluded(Field field) {
if (!bb.getHostVM().platformSupported(field)) {
return false;
}
return bb.getHostVM().isFieldIncluded(bb, field);
}

/**
* Includes the given class in the image.
*/
public void includeClass(Class<?> cls) {
/*
* Those classes cannot be registered as allocated as they cannot be instantiated. They are
* instead registered as reachable as they can still have methods or fields that could be
* used by an extension image.
*/
if (Modifier.isAbstract(cls.getModifiers()) || cls.isInterface() || cls.isPrimitive()) {
bb.getMetaAccess().lookupJavaType(cls).registerAsReachable(reason);
} else {
bb.getMetaAccess().lookupJavaType(cls).registerAsAllocated(reason);
}
}

/**
* Includes the given method in the image.
*/
public abstract void includeMethod(Executable method);

/**
* Includes the given field in the image.
*/
public void includeField(Field field) {
bb.postTask(debug -> bb.addRootField(field));
}

/**
* The analysis for the base layer of a layered image assumes that any method that is reachable
* using the base java access rules can be an entry point. An upper layer does not have access
* to the packages from a lower layer. Thus, only the public classes with their public and
* protected inner classes and methods can be accessed by an upper layer.
* <p>
* Protected elements from a final or sealed class cannot be accessed as an upper layer cannot
* create a new class that extends the final or sealed class.
* <p>
* All the fields are included disregarding access rules as a missing field would cause issues
* in the object layout.
*/
public static class LayeredBaseImageInclusionPolicy extends ClassInclusionPolicy {
public LayeredBaseImageInclusionPolicy(Object reason) {
super(reason);
}

@Override
public boolean isClassIncluded(Class<?> cls) {
Class<?> enclosingClass = cls.getEnclosingClass();
int classModifiers = cls.getModifiers();
if (enclosingClass != null) {
return isAccessible(enclosingClass, classModifiers) && isClassIncluded(enclosingClass);
} else {
return Modifier.isPublic(classModifiers);
}
}

@Override
public boolean isMethodIncluded(Executable method) {
return !Modifier.isAbstract(method.getModifiers()) && isAccessible(method) && super.isMethodIncluded(method);
}

@Override
public void includeMethod(Executable method) {
bb.postTask(debug -> {
/*
* Non-abstract methods from an abstract class or default methods from an interface
* are not registered as implementation invoked by the analysis because their
* declaring class cannot be marked as instantiated and AnalysisType.getTypeFlow
* only includes instantiated types (see TypeFlow.addObserver). For now, to ensure
* those methods are included in the image, they are manually registered as
* implementation invoked.
*/
Class<?> declaringClass = method.getDeclaringClass();
if (!Modifier.isAbstract(method.getModifiers()) && (declaringClass.isInterface() || Modifier.isAbstract(declaringClass.getModifiers()))) {
AnalysisMethod analysisMethod = bb.getMetaAccess().lookupJavaMethod(method);
analysisMethod.registerAsDirectRootMethod(reason);
analysisMethod.registerAsImplementationInvoked(reason);
}
bb.forcedAddRootMethod(method, false, reason);
});
}
}

/**
* The default inclusion policy. Includes all classes and methods. Including all fields causes
* issues at the moment, so the same rules as for the {@link LayeredBaseImageInclusionPolicy}
* are used.
*/
public static class DefaultAllInclusionPolicy extends ClassInclusionPolicy {
public DefaultAllInclusionPolicy(Object reason) {
super(reason);
}

@Override
public boolean isClassIncluded(Class<?> cls) {
return true;
}

@Override
public void includeMethod(Executable method) {
bb.postTask(debug -> bb.addRootMethod(method, false, reason));
}
}

protected boolean isAccessible(Member member) {
Class<?> cls = member.getDeclaringClass();
int modifiers = member.getModifiers();
return isAccessible(cls, modifiers);
}

protected boolean isAccessible(Class<?> cls, int modifiers) {
return Modifier.isPublic(modifiers) || (!Modifier.isFinal(cls.getModifiers()) && !cls.isSealed() && Modifier.isProtected(modifiers));
}
}
Loading

0 comments on commit 6df04c9

Please sign in to comment.