Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-55547] Introduce parsing stages to improve static final field folding. #10338

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,15 @@ public abstract class HostVM {

protected final OptionValues options;
protected final ClassLoader classLoader;
protected final List<BiConsumer<AnalysisMethod, StructuredGraph>> methodAfterBytecodeParsedListeners;
protected final List<BiConsumer<AnalysisMethod, StructuredGraph>> methodAfterParsingListeners;
private final List<BiConsumer<DuringAnalysisAccess, Class<?>>> classReachabilityListeners;
protected HostedProviders providers;

protected HostVM(OptionValues options, ClassLoader classLoader) {
this.options = options;
this.classLoader = classLoader;
this.methodAfterBytecodeParsedListeners = new CopyOnWriteArrayList<>();
this.methodAfterParsingListeners = new CopyOnWriteArrayList<>();
this.classReachabilityListeners = new ArrayList<>();
}
Expand Down Expand Up @@ -215,12 +217,32 @@ public String getImageName() {
public void recordActivity() {
}

public void addMethodAfterParsingListener(BiConsumer<AnalysisMethod, StructuredGraph> methodAfterParsingHook) {
methodAfterParsingListeners.add(methodAfterParsingHook);
public void addMethodAfterBytecodeParsedListener(BiConsumer<AnalysisMethod, StructuredGraph> listener) {
methodAfterBytecodeParsedListeners.add(listener);
}

public void addMethodAfterParsingListener(BiConsumer<AnalysisMethod, StructuredGraph> listener) {
methodAfterParsingListeners.add(listener);
}

/**
* Can be overwritten to run code after the bytecode of a method is parsed. This hook is
* guaranteed to be invoked before
* {@link #methodAfterParsingHook(BigBang, AnalysisMethod, StructuredGraph)} .
*
* @param bb the analysis engine
* @param method the newly parsed method
* @param graph the method graph
*/
public void methodAfterBytecodeParsedHook(BigBang bb, AnalysisMethod method, StructuredGraph graph) {
for (BiConsumer<AnalysisMethod, StructuredGraph> listener : methodAfterBytecodeParsedListeners) {
listener.accept(method, graph);
}
}

/**
* Can be overwritten to run code after a method is parsed.
* Can be overwritten to run code after a method is parsed and all pre-analysis optimizations
* are finished. This hook will be invoked before the graph is made available to the analysis.
*
* @param bb the analysis engine
* @param method the newly parsed method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import jdk.graal.compiler.debug.DebugContext.Description;
import jdk.graal.compiler.debug.Indent;
import jdk.graal.compiler.nodes.EncodedGraph;
import jdk.graal.compiler.nodes.GraphDecoder;
import jdk.graal.compiler.nodes.GraphEncoder;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
Expand All @@ -55,6 +56,63 @@

public final class AnalysisParsedGraph {

/**
* Analysis graph parsing is (currently) done in two stages. This is necessary to break cyclic
* dependencies between methods when performing pre-analysis optimizations. For example, if an
* optimization {@code Opt0} processes the graph of {@code methodA} on {@code threadA}, it will
* hold a lock during this operation. Assume {@code Opt0} requires to access the graph of
* {@code methodB}. This will issue a parsing request that may be executed by a different thread
* {@code threadB}. Now, it may be that {@code Opt0} also processes the graph of {@code methodB}
* and to do so, it needs to access the graph of {@code methodA}. This would end up in a
* deadlock. Staged parsing avoids this problem. In stage {@link #BYTECODE_PARSED}, only
* bytecode parsing will be done and any pre-analysis optimizations run in stage
* {@link #AFTER_PARSING_HOOKS_DONE}. Hence, {@code Opt0} can request the graph of the previous
* stage to break the cycle.
*
* Right now, it is not supported to add further parsing stages, and we are limited to the
* existing two stages. If further parsing stages are required, we first need to figure out: (1)
* if and how the stage graphs should be persisted for layered images, and (2) how to specify if
* a stage graph can be skipped if a later stage was requested.
*/
public enum Stage {
/**
* This stage only performs bytecode parsing for the requested method. No additional
* optimizations are applied except of any graph builder plugins that are used by the
* bytecode parser.
*/
BYTECODE_PARSED(null),

/**
* This stage performs additional after-parsing optimizations before the graph is published.
*/
AFTER_PARSING_HOOKS_DONE(BYTECODE_PARSED);

private final Stage previous;

Stage(Stage previous) {
this.previous = previous;
}

public static Stage finalStage() {
return AFTER_PARSING_HOOKS_DONE;
}

public Stage previous() {
return previous;
}

public boolean hasPrevious() {
return previous != null;
}

public static boolean isRequiredStage(Stage stage, AnalysisMethod method) {
return switch (stage) {
case BYTECODE_PARSED -> method.isClassInitializer();
case AFTER_PARSING_HOOKS_DONE -> true;
};
}
}

/**
* The architecture that the image builder is running on. This determines whether unaligned
* memory accesses are available for graph encoding / decoding at image build time.
Expand Down Expand Up @@ -83,8 +141,13 @@ public boolean isIntrinsic() {
return isIntrinsic;
}

@SuppressWarnings("try")
public static AnalysisParsedGraph parseBytecode(BigBang bb, AnalysisMethod method) {
return parseBytecodeForStage(bb, method, Stage.BYTECODE_PARSED);

}

@SuppressWarnings("try")
private static AnalysisParsedGraph parseBytecodeForStage(BigBang bb, AnalysisMethod method, Stage stage) {
if (bb == null) {
throw AnalysisError.shouldNotReachHere("BigBang object required for parsing method " + method.format("%H.%p(%n)"));
}
Expand All @@ -98,7 +161,7 @@ public static AnalysisParsedGraph parseBytecode(BigBang bb, AnalysisMethod metho
Object result = bb.getHostVM().parseGraph(bb, debug, method);
if (result != HostVM.PARSING_UNHANDLED) {
if (result instanceof StructuredGraph) {
return optimizeAndEncode(bb, method, (StructuredGraph) result, false);
return optimizeAndEncode(bb, method, (StructuredGraph) result, false, stage);
} else {
assert result == HostVM.PARSING_FAILED : result;
return EMPTY;
Expand All @@ -107,15 +170,15 @@ public static AnalysisParsedGraph parseBytecode(BigBang bb, AnalysisMethod metho

StructuredGraph graph = method.buildGraph(debug, method, bb.getProviders(method), Purpose.ANALYSIS);
if (graph != null) {
return optimizeAndEncode(bb, method, graph, false);
return optimizeAndEncode(bb, method, graph, false, stage);
}

InvocationPlugin plugin = bb.getProviders(method).getGraphBuilderPlugins().getInvocationPlugins().lookupInvocation(method, options);
if (plugin != null && !plugin.inlineOnly()) {
Bytecode code = new ResolvedJavaMethodBytecode(method);
graph = new SubstrateIntrinsicGraphBuilder(options, debug, bb.getProviders(method), code).buildGraph(plugin);
if (graph != null) {
return optimizeAndEncode(bb, method, graph, true);
return optimizeAndEncode(bb, method, graph, true, stage);
}
}

Expand Down Expand Up @@ -149,18 +212,69 @@ public static AnalysisParsedGraph parseBytecode(BigBang bb, AnalysisMethod metho
} catch (Throwable e) {
throw debug.handle(e);
}
return optimizeAndEncode(bb, method, graph, false);
return optimizeAndEncode(bb, method, graph, false, stage);
}
}

/**
* Creates the final stage (i.e. {@link Stage#AFTER_PARSING_HOOKS_DONE}) graph for the given
* method.
*
* There are two ways for how the final stage graph can be created: The stage 1 graph is (1)
* available, or (2) is not available.
*
* For {@code (1)}, the graph was usually created with {@link #parseBytecode} (i.e. stage
* {@link Stage#BYTECODE_PARSED}) but this is neither a strong requirement nor enforced. The
* graph will then be decoded, the {@link HostVM#methodAfterParsingHook after parsing hook} will
* be called, and again encoded.
*
* For {@code (2)} (if the input graph is {@code null}), the final stage graph will directly be
* created. In particular, this will parse the method's bytecode and call
* {@link #optimizeAndEncode} directly for stage {@link Stage#AFTER_PARSING_HOOKS_DONE}. This
* means that the {@link HostVM#methodAfterParsingHook after parsing hook} will ONLY be called
* for {@link Stage#AFTER_PARSING_HOOKS_DONE}.
*/
public static AnalysisParsedGraph createFinalStage(BigBang bb, AnalysisMethod method, AnalysisParsedGraph stage1Graph) {
if (stage1Graph == null) {
return parseBytecodeForStage(bb, method, Stage.AFTER_PARSING_HOOKS_DONE);
}
if (stage1Graph.encodedGraph == null) {
return EMPTY;
}
return optimizeAndEncode(bb, method, decodeParsedGraph(bb, method, stage1Graph), stage1Graph.isIntrinsic, Stage.AFTER_PARSING_HOOKS_DONE);
}

@SuppressWarnings("try")
private static StructuredGraph decodeParsedGraph(BigBang bb, AnalysisMethod method, AnalysisParsedGraph analysisParsedGraph) {
DebugContext.Description description = new DebugContext.Description(method, ClassUtil.getUnqualifiedName(method.getClass()) + ":" + method.getId());
DebugContext debug = new DebugContext.Builder(bb.getOptions(), new GraalDebugHandlersFactory(bb.getSnippetReflectionProvider())).description(description).build();

StructuredGraph result = new StructuredGraph.Builder(bb.getOptions(), debug, bb.getHostVM().allowAssumptions(method))
.method(method)
.trackNodeSourcePosition(analysisParsedGraph.encodedGraph.trackNodeSourcePosition())
.recordInlinedMethods(analysisParsedGraph.encodedGraph.isRecordingInlinedMethods())
.build();

try (DebugContext.Scope s = debug.scope("ClosedWorldAnalysis", result, method)) {
GraphDecoder decoder = new GraphDecoder(HOST_ARCHITECTURE, result);
decoder.decode(analysisParsedGraph.encodedGraph);
return result;
} catch (Throwable ex) {
throw debug.handle(ex);
}
}

@SuppressWarnings("try")
private static AnalysisParsedGraph optimizeAndEncode(BigBang bb, AnalysisMethod method, StructuredGraph graph, boolean isIntrinsic) {
private static AnalysisParsedGraph optimizeAndEncode(BigBang bb, AnalysisMethod method, StructuredGraph graph, boolean isIntrinsic, Stage stage) {
try (DebugContext.Scope s = graph.getDebug().scope("ClosedWorldAnalysis", graph, method)) {
/*
* Must be called before any other thread can access the graph, i.e., before the graph
* is published.
*/
bb.getHostVM().methodAfterParsingHook(bb, method, graph);
switch (stage) {
case BYTECODE_PARSED -> bb.getHostVM().methodAfterBytecodeParsedHook(bb, method, graph);
case AFTER_PARSING_HOOKS_DONE -> bb.getHostVM().methodAfterParsingHook(bb, method, graph);
}

EncodedGraph encodedGraph = GraphEncoder.encodeSingleGraph(graph, HOST_ARCHITECTURE);
return new AnalysisParsedGraph(encodedGraph, isIntrinsic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ public void persistMethodGraphs() {
}

private void persistAnalysisParsedGraph(AnalysisMethod method) {
Object analyzedGraph = method.getGraph();
Object analyzedGraph = method.getParsedGraphCacheStateObject();
if (analyzedGraph instanceof AnalysisParsedGraph analysisParsedGraph) {
String name = imageLayerSnapshotUtil.getMethodDescriptor(method);
MethodGraphsInfo graphsInfo = methodsMap.get(name);
Expand Down
Loading