Skip to content

Commit

Permalink
Introduce parsing stages to improve static final field folding.
Browse files Browse the repository at this point in the history
  • Loading branch information
fangerer committed Jan 7, 2025
1 parent b23f159 commit 2ef94cf
Show file tree
Hide file tree
Showing 8 changed files with 772 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,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 @@ -216,12 +218,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,67 @@

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 static Stage firstStage() {
return BYTECODE_PARSED;
}

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.isOriginalMethod() && 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 +145,29 @@ public boolean isIntrinsic() {
return isIntrinsic;
}

@SuppressWarnings("try")
private interface GraphOptimizer {
void optimize(BigBang bb, AnalysisMethod method, StructuredGraph graph);
}

private static void methodAfterBytecodeParsedHook(BigBang bb, AnalysisMethod method, StructuredGraph graph) {
bb.getHostVM().methodAfterBytecodeParsedHook(bb, method, graph);
}

private static void methodAfterParsingHook(BigBang bb, AnalysisMethod method, StructuredGraph graph) {
bb.getHostVM().methodAfterParsingHook(bb, method, graph);
}

private static void methodAfterBytecodeParsedAndOptimizationHook(BigBang bb, AnalysisMethod method, StructuredGraph graph) {
bb.getHostVM().methodAfterBytecodeParsedHook(bb, method, graph);
bb.getHostVM().methodAfterParsingHook(bb, method, graph);
}

public static AnalysisParsedGraph parseBytecode(BigBang bb, AnalysisMethod method) {
return parseBytecodeForStage(bb, method, AnalysisParsedGraph::methodAfterBytecodeParsedHook);
}

@SuppressWarnings("try")
private static AnalysisParsedGraph parseBytecodeForStage(BigBang bb, AnalysisMethod method, GraphOptimizer graphOptimizer) {
if (bb == null) {
throw AnalysisError.shouldNotReachHere("BigBang object required for parsing method " + method.format("%H.%p(%n)"));
}
Expand All @@ -98,7 +181,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, graphOptimizer);
} else {
assert result == HostVM.PARSING_FAILED : result;
return EMPTY;
Expand All @@ -107,15 +190,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, graphOptimizer);
}

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, graphOptimizer);
}
}

Expand Down Expand Up @@ -149,18 +232,66 @@ 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, graphOptimizer);
}
}

/**
* 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, AnalysisParsedGraph::methodAfterBytecodeParsedAndOptimizationHook);
}
if (stage1Graph.encodedGraph == null) {
return EMPTY;
}
return optimizeAndEncode(bb, method, decodeParsedGraph(bb, method, stage1Graph), stage1Graph.isIntrinsic, AnalysisParsedGraph::methodAfterParsingHook);
}

@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, GraphOptimizer graphOptimizer) {
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);
graphOptimizer.optimize(bb, method, graph);

EncodedGraph encodedGraph = GraphEncoder.encodeSingleGraph(graph, HOST_ARCHITECTURE);
return new AnalysisParsedGraph(encodedGraph, isIntrinsic);
Expand Down
Loading

0 comments on commit 2ef94cf

Please sign in to comment.