From 4e1b1dc34ce6ad15963743d0cbe6ba203839c539 Mon Sep 17 00:00:00 2001 From: Doug Simon Date: Wed, 26 Mar 2025 22:27:39 +0100 Subject: [PATCH] added support for heap dumping during compilation --- .../libgraal/LibGraalSupportImpl.java | 6 + .../management/JMXServiceProvider.java | 53 ++++++- .../compiler/core/test/DumpPathTest.java | 26 +++- .../graal/compiler/core/GraalCompiler.java | 35 +++-- .../compiler/core/GraalCompilerOptions.java | 10 ++ .../compiler/core/common/LibGraalSupport.java | 10 ++ .../graal/compiler/debug/DebugOptions.java | 73 +--------- .../jdk/graal/compiler/debug/GraphFilter.java | 77 +++++++++++ .../graal/compiler/debug/MethodFilter.java | 75 +++++++++- .../compiler/hotspot/CompilationTask.java | 31 ++--- .../jdk/graal/compiler/phases/BasePhase.java | 128 ++++++----------- .../graal/compiler/phases/PhaseFilterKey.java | 130 ++++++++++++++++++ .../serviceprovider/GraalServices.java | 16 +++ .../compiler/serviceprovider/JMXService.java | 9 ++ .../substitutions/GraalSubstitutions.java | 15 ++ 15 files changed, 505 insertions(+), 189 deletions(-) create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/GraphFilter.java create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseFilterKey.java diff --git a/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalSupportImpl.java b/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalSupportImpl.java index 219be44f2c4f..b22752e5b9ee 100644 --- a/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalSupportImpl.java +++ b/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalSupportImpl.java @@ -25,6 +25,7 @@ package jdk.graal.compiler.libgraal; import java.io.Closeable; +import java.io.IOException; import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -160,6 +161,11 @@ public void processReferences() { LibGraalRuntime.processReferences(); } + @Override + public void dumpHeap(String outputFile, boolean live) throws IOException { + VMRuntime.dumpHeap(outputFile, live); + } + @Override public long getIsolateAddress() { return CurrentIsolate.getIsolate().rawValue(); diff --git a/compiler/src/jdk.graal.compiler.management/src/jdk/graal/compiler/management/JMXServiceProvider.java b/compiler/src/jdk.graal.compiler.management/src/jdk/graal/compiler/management/JMXServiceProvider.java index 5626a5dc8351..2ffd81011ec0 100644 --- a/compiler/src/jdk.graal.compiler.management/src/jdk/graal/compiler/management/JMXServiceProvider.java +++ b/compiler/src/jdk.graal.compiler.management/src/jdk/graal/compiler/management/JMXServiceProvider.java @@ -26,16 +26,22 @@ import static jdk.graal.compiler.serviceprovider.GraalServices.getCurrentThreadId; +import java.io.IOException; import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; +import com.sun.management.HotSpotDiagnosticMXBean; import jdk.graal.compiler.serviceprovider.JMXService; import jdk.graal.compiler.serviceprovider.ServiceProvider; import com.sun.management.ThreadMXBean; +import javax.management.MBeanServer; + /** - * Implementation of {@link JMXService} for JDK 11+. + * Implementation of {@link JMXService}. */ @ServiceProvider(JMXService.class) public class JMXServiceProvider extends JMXService { @@ -66,4 +72,49 @@ protected boolean isCurrentThreadCpuTimeSupported() { protected List getInputArguments() { return ManagementFactory.getRuntimeMXBean().getInputArguments(); } + + /** + * Name of the HotSpot Diagnostic MBean. + */ + private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; + + private volatile HotSpotDiagnosticMXBean hotspotMXBean; + + @Override + protected void dumpHeap(String outputFile, boolean live) throws IOException { + initHotSpotMXBean(); + try { + Path path = Path.of(outputFile); + if (Files.exists(path) && Files.size(path) == 0) { + Files.delete(path); + } + hotspotMXBean.dumpHeap(outputFile, live); + } catch (RuntimeException re) { + throw re; + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } + + private void initHotSpotMXBean() { + if (hotspotMXBean == null) { + synchronized (this) { + if (hotspotMXBean == null) { + hotspotMXBean = getHotSpotMXBean(); + } + } + } + } + + private static HotSpotDiagnosticMXBean getHotSpotMXBean() { + try { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + return ManagementFactory.newPlatformMXBeanProxy(server, + HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); + } catch (RuntimeException re) { + throw re; + } catch (Exception exp) { + throw new RuntimeException(exp); + } + } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/DumpPathTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/DumpPathTest.java index fb5326251ecf..3c0cc5e36669 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/DumpPathTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/DumpPathTest.java @@ -28,7 +28,10 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import jdk.graal.compiler.core.GraalCompilerOptions; import org.graalvm.collections.EconomicMap; import org.junit.Test; @@ -53,7 +56,7 @@ public static Object snippet() { public void testDump() throws Exception { assumeManagementLibraryIsLoadable(); try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) { - String[] extensions = new String[]{".cfg", ".bgv", ".graph-strings"}; + String[] extensions = {".cfg", ".bgv", ".graph-strings"}; EconomicMap, Object> overrides = OptionValues.newOptionMap(); overrides.put(DebugOptions.DumpPath, temp.toString()); overrides.put(DebugOptions.ShowDumpFiles, false); @@ -61,6 +64,7 @@ public void testDump() throws Exception { overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File); overrides.put(DebugOptions.PrintCanonicalGraphStrings, true); overrides.put(DebugOptions.Dump, "*"); + overrides.put(GraalCompilerOptions.DumpHeapAfter, ":Schedule"); overrides.put(DebugOptions.MethodFilter, null); try (AutoCloseable c = new TTY.Filter()) { @@ -68,7 +72,20 @@ public void testDump() throws Exception { test(new OptionValues(getInitialOptions(), overrides), "snippet"); } // Check that IGV files got created, in the right place. - checkForFiles(temp.path, extensions); + List paths = checkForFiles(temp.path, extensions); + List compilationHeapDumps = new ArrayList<>(); + List phaseHeapDumps = new ArrayList<>(); + for (Path path : paths) { + String name = path.toString(); + if (name.endsWith(".compilation.hprof")) { + compilationHeapDumps.add(path); + } else if (name.endsWith(".hprof")) { + phaseHeapDumps.add(path); + } + } + + assertTrue(!compilationHeapDumps.isEmpty()); + assertTrue(!phaseHeapDumps.isEmpty()); } } @@ -76,10 +93,12 @@ public void testDump() throws Exception { * Check that the given directory contains file or directory names with all the given * extensions. */ - private static void checkForFiles(Path directoryPath, String[] extensions) throws IOException { + private static List checkForFiles(Path directoryPath, String[] extensions) throws IOException { String[] paths = new String[extensions.length]; + List result = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(directoryPath)) { for (Path filePath : stream) { + result.add(filePath); String fileName = filePath.getFileName().toString(); for (int i = 0; i < extensions.length; i++) { String extension = extensions[i]; @@ -97,5 +116,6 @@ private static void checkForFiles(Path directoryPath, String[] extensions) throw for (int i = 1; i < paths.length; i++) { assertTrue(paths[0].equals(paths[i]), paths[0] + " != " + paths[i]); } + return result; } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java index 7cbedf7f4825..8538ba97901b 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompiler.java @@ -24,6 +24,7 @@ */ package jdk.graal.compiler.core; +import java.io.IOException; import java.util.concurrent.TimeUnit; import jdk.graal.compiler.code.CompilationResult; @@ -35,8 +36,8 @@ import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.DebugContext.CompilerPhaseScope; import jdk.graal.compiler.debug.DebugOptions; +import jdk.graal.compiler.debug.GraphFilter; import jdk.graal.compiler.debug.MemUseTrackerKey; -import jdk.graal.compiler.debug.MethodFilter; import jdk.graal.compiler.debug.TTY; import jdk.graal.compiler.debug.TimerKey; import jdk.graal.compiler.lir.asm.CompilationResultBuilderFactory; @@ -53,6 +54,7 @@ import jdk.graal.compiler.phases.tiers.Suites; import jdk.graal.compiler.phases.tiers.TargetProvider; import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.vm.ci.meta.ProfilingInfo; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -157,10 +159,28 @@ public static T compile(Request r) { throw debug.handle(e); } checkForRequestedDelay(r.graph); + + checkForHeapDump(r, debug); + return r.compilationResult; } } + /** + * Checks if {@link GraalCompilerOptions#DumpHeapAfter} is enabled for the compilation in + * {@code request} and if so, dumps the heap to a file specified by the debug context. + */ + private static void checkForHeapDump(Request request, DebugContext debug) { + if (GraalCompilerOptions.DumpHeapAfter.matches(debug.getOptions(), null, request.graph)) { + try { + final String path = debug.getDumpPath(".compilation.hprof", false); + GraalServices.dumpHeap(path, false); + } catch (IOException e) { + e.printStackTrace(System.out); + } + } + } + /** * Support for extra processing of a crash triggered by {@link GraalCompilerOptions#CrashAt}. */ @@ -239,18 +259,7 @@ private static String match(StructuredGraph graph, String methodPattern) { // Absence of methodPattern means match everything return graph.name != null ? graph.name : graph.method().format("%H.%n(%p)"); } - String label = null; - if (graph.name != null && graph.name.contains(methodPattern)) { - label = graph.name; - } - if (label == null) { - ResolvedJavaMethod method = graph.method(); - MethodFilter filter = MethodFilter.parse(methodPattern); - if (filter.matches(method)) { - label = method.format("%H.%n(%p)"); - } - } - return label; + return new GraphFilter(methodPattern).matchedLabel(graph); } /** diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompilerOptions.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompilerOptions.java index 85d90a31c9c2..d1b93297f19b 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompilerOptions.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/GraalCompilerOptions.java @@ -30,6 +30,7 @@ import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionStability; import jdk.graal.compiler.options.OptionType; +import jdk.graal.compiler.phases.PhaseFilterKey; /** * Options related to {@link GraalCompiler}. @@ -46,6 +47,15 @@ Pattern for method(s) that will trigger an exception when compiled. suffix will raise a bailout exception and a ':PermanentBailout' suffix will raise a permanent bailout exception.""", type = OptionType.Debug) public static final OptionKey CrashAt = new OptionKey<>(null); + @Option(help = """ + Emit a heap dump after each phase matching the given phase filter(s). + + Use DumpPath or ShowDumpFiles to set or see where dumps are written. + + The special phase name "" means dump after compilation + instead of after any specific phase. + """ + PhaseFilterKey.HELP, type = OptionType.Debug)// + public static final PhaseFilterKey DumpHeapAfter = new PhaseFilterKey(null, ""); @Option(help = "Treats compilation bailouts as compilation failures.", type = OptionType.User, stability = OptionStability.STABLE) public static final OptionKey CompilationBailoutAsFailure = new OptionKey<>(false); @Option(help = """ diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/LibGraalSupport.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/LibGraalSupport.java index 6ed08eb90c53..d4bd8aba306c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/LibGraalSupport.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/LibGraalSupport.java @@ -26,6 +26,7 @@ import org.graalvm.collections.EconomicMap; +import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -112,6 +113,15 @@ public interface LibGraalSupport { */ void processReferences(); + /** + * Dumps the heap to {@code outputFile} in hprof format. + * + * @param live if true, performs a full GC first so that only live objects are dumped + * @throws IOException if an IO error occurred dyring dumping + * @throws UnsupportedOperationException if this operation is not supported. + */ + void dumpHeap(String outputFile, boolean live) throws IOException; + /** * Gets the address of the current isolate. */ diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java index 6751aa9294fd..304079ee4ad2 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/DebugOptions.java @@ -182,78 +182,7 @@ public enum OptimizationLogTarget { @Option(help = "Pattern for specifying scopes in which logging is enabled. " + "See the Dump option for the pattern syntax.", type = OptionType.Debug) public static final OptionKey Log = new OptionKey<>(null); - @Option(help = """ - Pattern for matching methods. - The syntax for a pattern is: - - SourcePatterns = SourcePattern ["," SourcePatterns] . - SourcePattern = [ "~" ] [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] . - Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" . - Class = { package "." } class . - - Glob pattern matching (*, ?) is allowed in all parts of the source pattern. - The "~" prefix negates the pattern. - - Positive patterns are joined by an "or" operator: "A,B" matches anything - matched by "A" or "B". Negative patterns are joined by "and not": "~A,~B" - matches anything not matched by "A" and not matched by "B". "A,~B,~C,D" - matches anything matched by "A" or "D" and not matched by "B" and not - matched by "C". - - A set of patterns containing negative patterns but no positive ones contains - an implicit positive "*" pattern: "~A,~B" is equivalent to "*,~A,~B". - - Examples of method filters: - --------- - * - - Matches all methods in all classes. - --------- - canonical(CanonicalizerTool;LogicNode;LogicNode) - - Matches all methods named "canonical", with the first parameter of type - "CanonicalizerTool", and the second and third parameters of type - "LogicNode". - The packages of the parameter types are irrelevant. - --------- - arraycopy(Object;;;;) - - Matches all methods named "arraycopy", with the first parameter - of type "Object", and four more parameters of any type. The - packages of the parameter types are irrelevant. - --------- - List.set - - Matches all methods named "set" in a class whose simple name is "List". - --------- - *List.set - - Matches all methods named "set" in a class whose simple name ends with "List". - --------- - jdk.graal.compiler.nodes.PhiNode.* - - Matches all methods in the class "jdk.graal.compiler.nodes.PhiNode". - --------- - jdk.graal.compiler.nodes.*.canonical - - Matches all methods named "canonical" in classes in the package - "jdk.graal.compiler.nodes". - --------- - arraycopy,toString - - Matches all methods named "arraycopy" or "toString", meaning that ',' acts - as an "or" operator. - --------- - java.util.*.*.,~java.util.*Array*.* - java.util.*.*.,~*Array*.* - - These patterns are equivalent and match all methods in the package - "java.util" except for classes that have "Array" in their name. - --------- - ~java.util.*.* - - Matches all methods in all classes in all packages except for anything in - the "java.util" package.""") + @Option(help = jdk.graal.compiler.debug.MethodFilter.HELP) public static final OptionKey MethodFilter = new OptionKey<>(null); @Option(help = "Only check MethodFilter against the root method in the context if true, otherwise check all methods", type = OptionType.Debug) public static final OptionKey MethodFilterRootOnly = new OptionKey<>(false); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/GraphFilter.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/GraphFilter.java new file mode 100644 index 000000000000..d9e1cbf169fd --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/GraphFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, 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 jdk.graal.compiler.debug; + +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * A filter that can be applied to {@linkplain jdk.graal.compiler.nodes.StructuredGraph graphs}. + */ +public final class GraphFilter { + + private final String value; + private final MethodFilter methodFilter; + + /// Creates a graph filter. + /// A graph is matched by this filter if any of the following is true: + /// * `value` is null + /// * [StructuredGraph#name] is non-null and contains `value` as a substring + /// * [StructuredGraph#method()] is non-null and is [matched][MethodFilter#matches] by the + /// [MethodFilter] parsed from `value`` + public GraphFilter(String value) { + this.value = value; + this.methodFilter = value == null ? MethodFilter.matchAll() : MethodFilter.parse(value); + } + + @Override + public String toString() { + return value + ":" + methodFilter; + } + + /// Determines if this filter matches `graph` based on the rules described in [GraphFilter]. + public boolean matches(StructuredGraph graph) { + if (graph.name != null) { + if (value == null || graph.name.contains(value)) { + return true; + } + } + ResolvedJavaMethod method = graph.method(); + if (method != null) { + return methodFilter.matches(method); + } + return false; + } + + /// If this filter matches `graph` based on the rules described in [GraphFilter], + /// returns a label for `graph` based on its non-null name or non-null method. + /// Otherwise, returns `null`. + public String matchedLabel(StructuredGraph graph) { + if (matches(graph)) { + return graph.name != null ? graph.name : graph.method().format("%H.%n(%p)"); + } + return null; + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/MethodFilter.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/MethodFilter.java index f3a63f7a64fe..dc74fc1d3dca 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/MethodFilter.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/debug/MethodFilter.java @@ -39,10 +39,83 @@ /** * This class implements a method filter that can filter based on class name, method name and * parameters. This filter is a collection of "base filters", each of which may be negated. The - * syntax for a filter is explained here. + * syntax for a filter is explained by {@link #HELP}. */ public final class MethodFilter { + public static final String HELP = """ + Pattern for matching methods. + The syntax for a pattern is: + + SourcePatterns = SourcePattern ["," SourcePatterns] . + SourcePattern = [ "~" ] [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] . + Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" . + Class = { package "." } class . + + Glob pattern matching (*, ?) is allowed in all parts of the source pattern. + The "~" prefix negates the pattern. + + Positive patterns are joined by an "or" operator: "A,B" matches anything + matched by "A" or "B". Negative patterns are joined by "and not": "~A,~B" + matches anything not matched by "A" and not matched by "B". "A,~B,~C,D" + matches anything matched by "A" or "D" and not matched by "B" and not + matched by "C". + + A set of patterns containing negative patterns but no positive ones contains + an implicit positive "*" pattern: "~A,~B" is equivalent to "*,~A,~B". + + Examples of method filters: + --------- + * + + Matches all methods in all classes. + --------- + canonical(CanonicalizerTool;LogicNode;LogicNode) + + Matches all methods named "canonical", with the first parameter of type + "CanonicalizerTool", and the second and third parameters of type + "LogicNode". + The packages of the parameter types are irrelevant. + --------- + arraycopy(Object;;;;) + + Matches all methods named "arraycopy", with the first parameter + of type "Object", and four more parameters of any type. The + packages of the parameter types are irrelevant. + --------- + List.set + + Matches all methods named "set" in a class whose simple name is "List". + --------- + *List.set + + Matches all methods named "set" in a class whose simple name ends with "List". + --------- + jdk.graal.compiler.nodes.PhiNode.* + + Matches all methods in the class "jdk.graal.compiler.nodes.PhiNode". + --------- + jdk.graal.compiler.nodes.*.canonical + + Matches all methods named "canonical" in classes in the package + "jdk.graal.compiler.nodes". + --------- + arraycopy,toString + + Matches all methods named "arraycopy" or "toString", meaning that ',' acts + as an "or" operator. + --------- + java.util.*.*.,~java.util.*Array*.* + java.util.*.*.,~*Array*.* + + These patterns are equivalent and match all methods in the package + "java.util" except for classes that have "Array" in their name. + --------- + ~java.util.*.* + + Matches all methods in all classes in all packages except for anything in + the "java.util" package."""; + private final ArrayList positiveFilters; private final ArrayList negativeFilters; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationTask.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationTask.java index 74bdf2bd7051..a6d544023696 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationTask.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationTask.java @@ -77,17 +77,18 @@ public class CompilationTask implements CompilationWatchDog.EventHandler { - static class Options { - @Option(help = "Options which are enabled based on the method being compiled. " + - "The basic syntax is a MethodFilter option specification followed by a list of options to be set for that compilation. " + - "\"MethodFilter:\" is used to distinguish this from normal usage of MethodFilter as option." + - "This can be repeated multiple times with each MethodFilter option separating the groups. " + - "For example:" + - " -D" + HotSpotGraalOptionValues.GRAAL_OPTION_PROPERTY_PREFIX + - ".PerMethodOptions=MethodFilter:String.indexOf SpeculativeGuardMovement=false MethodFilter:Integer.* SpeculativeGuardMovement=false" + - " disables SpeculativeGuardMovement for compiles of String.indexOf and all methods in Integer. " + - "If the value starts with a non-letter character, that " + - "character is used as the separator between options instead of a space.")// + public static class Options { + @Option(help = """ + Options which are enabled based on the method being compiled. + The basic syntax is a MethodFilter option specification followed by a list of options to be set for that compilation. + "MethodFilter:" is used to distinguish this from normal usage of MethodFilter as option. + This can be repeated multiple times with each MethodFilter option separating the groups. + For example: + " -D""" + HotSpotGraalOptionValues.GRAAL_OPTION_PROPERTY_PREFIX + """ + PerMethodOptions=MethodFilter:String.indexOf SpeculativeGuardMovement=false MethodFilter:Integer.* SpeculativeGuardMovement=false + disables SpeculativeGuardMovement for compiles of String.indexOf and all methods in Integer. + If the value starts with a non-letter character, that + character is used as the separator between options instead of a space.""")// public static final OptionKey PerMethodOptions = new OptionKey<>(null); } @@ -157,8 +158,7 @@ protected void parseRetryOptions(String[] options, EconomicMap, Obj @Override protected HotSpotCompilationRequestResult handleException(Throwable t) { - if (t instanceof BailoutException) { - BailoutException bailout = (BailoutException) t; + if (t instanceof BailoutException bailout) { /* * Handling of permanent bailouts: Permanent bailouts that can happen for example * due to unsupported unstructured control flow in the bytecodes of a method must @@ -190,8 +190,7 @@ protected void dumpOnError(DebugContext errorContext, Throwable cause) { @Override protected ExceptionAction lookupAction(OptionValues values, Throwable cause) { - if (cause instanceof BailoutException) { - BailoutException bailout = (BailoutException) cause; + if (cause instanceof BailoutException bailout) { if (bailout.isPermanent()) { // Respect current action if it has been explicitly set. if (!CompilationBailoutAsFailure.hasBeenSet(values)) { @@ -483,7 +482,7 @@ public HotSpotCompilationRequestResult runCompilation(OptionValues initialOption } } - @SuppressWarnings({"try", "unchecked"}) + @SuppressWarnings({"try"}) public HotSpotCompilationRequestResult runCompilation(DebugContext debug) { try (DebugCloseable a = CompilationTime.start(debug)) { HotSpotCompilationRequestResult result = runCompilation(debug, new HotSpotCompilationWrapper()); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/BasePhase.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/BasePhase.java index 2e10807be906..852e595ecd24 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/BasePhase.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/BasePhase.java @@ -26,14 +26,17 @@ import static jdk.graal.compiler.debug.DebugOptions.PrintUnmodifiedGraphs; +import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; +import jdk.graal.compiler.core.GraalCompilerOptions; +import jdk.graal.compiler.debug.GraphFilter; +import jdk.graal.compiler.serviceprovider.GraalServices; import org.graalvm.collections.EconomicMap; import jdk.graal.compiler.core.common.util.CompilationAlarm; @@ -44,7 +47,6 @@ import jdk.graal.compiler.debug.DebugOptions; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.debug.MemUseTrackerKey; -import jdk.graal.compiler.debug.MethodFilter; import jdk.graal.compiler.debug.TTY; import jdk.graal.compiler.debug.TimerKey; import jdk.graal.compiler.graph.Graph; @@ -63,7 +65,6 @@ import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.phases.contract.NodeCostUtil; import jdk.graal.compiler.phases.contract.PhaseSizeContract; -import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.SpeculationLog; /** @@ -157,7 +158,7 @@ public static Optional unlessRunAfter(BasePhase phase, StageFl * If {@code graphState} is after stage {@code flag}, returns a {@link NotApplicable} * explaining that {@code phase} must be run after stage {@code flag}. Otherwise, returns * {@link #ALWAYS_APPLICABLE}. - * + *

* This is equivalent to {@link #unlessRunBefore} but is preferred to identify phases that * can be applied at most once. */ @@ -190,21 +191,18 @@ public static Optional ifAny(Optional... constrain } + /** + * Options applying to all phases. This class is not named {@code Options} to avoid collision + * with options defined in subclasses of {@link BasePhase}. + */ public static class PhaseOptions { // @formatter:off @Option(help = "Verify before - after relation of the relative, computed, code size of a graph", type = OptionType.Debug) public static final OptionKey VerifyGraalPhasesSize = new OptionKey<>(false); @Option(help = "Minimal size in NodeSize to check the graph size increases of phases.", type = OptionType.Debug) public static final OptionKey MinimalGraphNodeSizeCheckSize = new OptionKey<>(1000); - @Option(help = """ - Exclude certain phases from compilation, either unconditionally or with a method filter. - Multiple exclusions can be specified separated by ':'. - Phase names are matched as substrings, e.g.: - CompilationExcludePhases=PartialEscape:Loop=A.*,B.foo excludes - PartialEscapePhase from all compilations and any phase containing - 'Loop' in its name from compilations of all methods in class A and of - method B.foo.""", type = OptionType.Debug) - public static final OptionKey CompilationExcludePhases = new OptionKey<>(null); + @Option(help = "Exclude certain phases from compilation based on the given phase filter(s)." + PhaseFilterKey.HELP, type = OptionType.Debug) + public static final PhaseFilterKey CompilationExcludePhases = new PhaseFilterKey(null, null); // @formatter:on } @@ -319,7 +317,6 @@ public boolean shouldApply(StructuredGraph graph) { * applying} this phase to a graph whose state is {@code graphState}. * * @param graphState the state of graph to which the caller wants to apply this phase - * * @return a {@link NotApplicable} detailing why this phase cannot be applied or a value equal * to {@link Optional#empty} if the phase can be applied */ @@ -393,7 +390,7 @@ public interface ApplyScope { /** * Return an {@link ApplyScope} which will surround all the work performed by the call to - * {@link #run} in {@link #apply(StructuredGraph, Object, boolean)}. This allows subclaseses to + * {@link #run} in {@link #apply(StructuredGraph, Object, boolean)}. This allows subclasses to * inject work which will performed before and after the application of this phase. */ @SuppressWarnings("unused") @@ -436,8 +433,9 @@ public final void apply(final StructuredGraph graph, final C context, final bool } } - if (ExcludePhaseFilter.exclude(graph.getOptions(), this, graph.asJavaMethod())) { - TTY.println("excluding " + getName() + " during compilation of " + graph.asJavaMethod().format("%H.%n(%p)")); + if (PhaseOptions.CompilationExcludePhases.matches(options, this, graph)) { + String label = graph.name != null ? graph.name : graph.method().format("%H.%n(%p)"); + TTY.println("excluding " + getName() + " during compilation of " + label); return; } @@ -499,10 +497,20 @@ public final void apply(final StructuredGraph graph, final C context, final bool /* * Reset the progress-based compilation alarm to ensure that progress tracking happens * for each phase in isolation. This prevents false alarms where the same progress state - * is seen in subsequent phases, e.g., during graph verification a the end of each + * is seen in subsequent phases, e.g., during graph verification at the end of each * phase. */ CompilationAlarm.resetProgressDetection(); + + if (GraalCompilerOptions.DumpHeapAfter.matches(options, this, graph)) { + try { + final String path = debug.getDumpPath("_" + getName() + ".hprof", false); + GraalServices.dumpHeap(path, false); + } catch (IOException e) { + e.printStackTrace(System.out); + } + } + } catch (Throwable t) { throw debug.handle(t); } @@ -545,7 +553,7 @@ private boolean shouldDump(StructuredGraph graph, C context) { try (DebugContext.Scope s2 = debug.sandbox("GraphChangeListener", null)) { run(graphCopy, context); } catch (Throwable t) { - debug.handle(t); + throw debug.handle(t); } } return listener.changed; @@ -589,88 +597,42 @@ public float codeSizeIncrease() { return 1.25f; } - private static final class ExcludePhaseFilter { + /** + * A phase filter parsed from a single phase specification. + */ + static final class PhaseFilter { /** - * Contains the excluded phases and the corresponding methods to exclude. + * @see PhaseFilterKey#phaselessGraphFilterToken */ - private final EconomicMap filters; + private final GraphFilter phaselessGraphFilter; /** - * Cache instances of this class to avoid parsing the same option string more than once. + * A map from a phase pattern to a method pattern. */ - private static final ConcurrentHashMap instances; - - static { - instances = new ConcurrentHashMap<>(); - } + private final EconomicMap filters; /** - * Determines whether the phase should be excluded from running on the given method based on - * the given option values. + * Determines whether this filter matches {@code phase} and {@code graph}. */ - protected static boolean exclude(OptionValues options, BasePhase phase, JavaMethod method) { - String compilationExcludePhases = PhaseOptions.CompilationExcludePhases.getValue(options); - if (compilationExcludePhases == null) { + boolean matches(BasePhase phase, StructuredGraph graph) { + if (graph == null) { return false; - } else { - return getInstance(compilationExcludePhases).exclude(phase, method); } - } - - /** - * Gets an instance of this class for the given option values. This will typically be a - * cached instance. - */ - private static ExcludePhaseFilter getInstance(String compilationExcludePhases) { - return instances.computeIfAbsent(compilationExcludePhases, excludePhases -> ExcludePhaseFilter.parse(excludePhases)); - } - - /** - * Determines whether the given phase should be excluded from running on the given method. - */ - protected boolean exclude(BasePhase phase, JavaMethod method) { - if (method == null) { - return false; + if (phase == null) { + return phaselessGraphFilter.matches(graph); } String phaseName = phase.getClass().getSimpleName(); - for (Pattern excludedPhase : filters.getKeys()) { - if (excludedPhase.matcher(phaseName).matches()) { - return filters.get(excludedPhase).matches(method); + for (Pattern phasePattern : filters.getKeys()) { + if (phasePattern.matcher(phaseName).matches()) { + return filters.get(phasePattern).matches(graph); } } return false; } - /** - * Creates a phase filter based on a specification string. The string is a colon-separated - * list of phase names or {@code phase_name=filter} pairs. Phase names match any phase of - * which they are a substring. Filters follow {@link MethodFilter} syntax. - */ - private static ExcludePhaseFilter parse(String compilationExcludePhases) { - EconomicMap filters = EconomicMap.create(); - String[] parts = compilationExcludePhases.trim().split(":"); - for (String part : parts) { - String phaseName; - MethodFilter methodFilter; - if (part.contains("=")) { - String[] pair = part.split("="); - if (pair.length != 2) { - throw new IllegalArgumentException("expected phase_name=filter pair in: " + part); - } - phaseName = pair[0]; - methodFilter = MethodFilter.parse(pair[1]); - } else { - phaseName = part; - methodFilter = MethodFilter.matchAll(); - } - Pattern phasePattern = Pattern.compile(".*" + MethodFilter.createGlobString(phaseName) + ".*"); - filters.put(phasePattern, methodFilter); - } - return new ExcludePhaseFilter(filters); - } - - private ExcludePhaseFilter(EconomicMap filters) { + PhaseFilter(EconomicMap filters, GraphFilter phaselessMethodFilter) { + this.phaselessGraphFilter = phaselessMethodFilter; this.filters = filters; } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseFilterKey.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseFilterKey.java new file mode 100644 index 000000000000..6cd35eaf7e9b --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseFilterKey.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, 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 jdk.graal.compiler.phases; + +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.debug.GraphFilter; +import jdk.graal.compiler.debug.MethodFilter; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionValues; +import org.graalvm.collections.EconomicMap; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * An option that accepts a phase filter value. See {@link #HELP} for details. + */ +public final class PhaseFilterKey extends OptionKey { + + /** + * The token in a phase filter value for denoting a filter to be used in a phase free context + * (i.e. match against graphs). For example, the token {@code ""} can be used for a + * filter that operates on whole compilations instead of individual phases. If null, then the + * {@code phase} argument to {@link #matches} cannot be null. + */ + final String phaselessGraphFilterToken; + + public PhaseFilterKey(String defaultValue, String phaselessGraphFilterToken) { + super(defaultValue); + this.phaselessGraphFilterToken = phaselessGraphFilterToken; + } + + /** + * Option help message describing the syntax of a phase filter specification. + */ + public static final String HELP = """ + + A phase filter is a phase name, optionally followed by '=' and a method + filter. Multiple phase filters can be specified, separated by ':'. + A phase name is matched as a substring. + + For example: + + PartialEscape:Loop=B.foo,A.* + + matches PartialEscapePhase for compilation of all methods and any phase + containing "Loop" in its name for compilation of B.foo as well as all + methods in class A. + + A phase filter specification cannot be empty. Specify "*" to match + any phase."""; + + private final ConcurrentHashMap parsedValues = new ConcurrentHashMap<>(); + + @Override + protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) { + if (newValue != null && newValue.isEmpty()) { + throw new IllegalArgumentException("Value for " + getName() + " cannot be empty"); + } + } + + /** + * Determines whether {@code phase} when applied to {@code graph} is matched by {@code filter}. + */ + public boolean matches(OptionValues options, BasePhase phase, StructuredGraph graph) { + String filter = getValue(options); + if (filter == null) { + return false; + } else { + GraalError.guarantee(phase != null || phaselessGraphFilterToken != null, "Cannot pass null phase unless phaselessGraphFilterToken is non-null"); + return parsedValues.computeIfAbsent(filter, f -> PhaseFilterKey.parse(f, phaselessGraphFilterToken)).matches(phase, graph); + } + } + + /** + * Creates a phase filter based on a specification string. The string is a colon-separated list + * of phase names or {@code phase_name=filter} pairs. Phase names match any phase of which they + * are a substring. Filters follow {@link MethodFilter} syntax. + */ + private static BasePhase.PhaseFilter parse(String specification, String phaselessGraphFilterToken) { + EconomicMap filters = EconomicMap.create(); + String[] parts = specification.trim().split(":"); + GraphFilter phaselessGraphFilter = null; + for (String part : parts) { + String phaseName; + GraphFilter graphFilter; + if (part.contains("=")) { + String[] pair = part.split("="); + if (pair.length != 2) { + throw new IllegalArgumentException("expected phase_name=filter pair in: " + part); + } + phaseName = pair[0]; + graphFilter = new GraphFilter(pair[1]); + } else { + phaseName = part; + graphFilter = new GraphFilter(null); + } + if (phaseName.equals(phaselessGraphFilterToken)) { + phaselessGraphFilter = graphFilter; + } else { + Pattern phasePattern = Pattern.compile(".*" + MethodFilter.createGlobString(phaseName) + ".*"); + filters.put(phasePattern, graphFilter); + } + } + return new BasePhase.PhaseFilter(filters, phaselessGraphFilter); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java index f205890a1d05..9c0f04a5b0f1 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/GraalServices.java @@ -491,6 +491,22 @@ public static List getInputArguments() { return jmx.getInputArguments(); } + /** + * Dumps the heap to {@code outputFile} in hprof format. + * + * @param live if true, performs a full GC first so that only live objects are dumped + * @throws IOException if an IO error occurred dyring dumping + * @throws UnsupportedOperationException if this operation is not supported. + */ + public static void dumpHeap(String outputFile, boolean live) throws IOException { + LibGraalSupport libgraal = LibGraalSupport.INSTANCE; + if (libgraal != null) { + libgraal.dumpHeap(outputFile, live); + } else if (jmx != null) { + jmx.dumpHeap(outputFile, live); + } + } + /** * Returns the fused multiply add of the three arguments; that is, returns the exact product of * the first two arguments summed with the third argument and then rounded once to the nearest diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/JMXService.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/JMXService.java index c158cec5b54a..8d18f35367ea 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/JMXService.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/serviceprovider/JMXService.java @@ -24,6 +24,7 @@ */ package jdk.graal.compiler.serviceprovider; +import java.io.IOException; import java.util.List; /** @@ -42,4 +43,12 @@ public abstract class JMXService { protected abstract boolean isCurrentThreadCpuTimeSupported(); protected abstract List getInputArguments(); + + /** + * Dumps the heap to {@code outputFile} in hprof format. + * + * @param live if true, performs a full GC first so that only live objects are dumped + * @throws IOException if an IO error occurred dyring dumping + */ + protected abstract void dumpHeap(String outputFile, boolean live) throws IOException; } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java index 4f9a2c576a5b..5a9a74435a26 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/substitutions/GraalSubstitutions.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.Custom; import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FromAlias; +import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @@ -40,6 +41,7 @@ import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.SubstrateTargetDescription; @@ -222,6 +224,19 @@ public static long getIsolateID() { } } +@TargetClass(className = "jdk.graal.compiler.serviceprovider.GraalServices", onlyWith = GraalCompilerFeature.IsEnabled.class) +final class Target_jdk_graal_compiler_serviceprovider_GraalServices { + + /** + * This substitution is required to bypass the HotSpot specific MXBean used in + * {@code jdk.graal.compiler.management.JMXServiceProvider}. + */ + @Substitute + public static void dumpHeap(String outputFile, boolean live) throws IOException { + VMRuntime.dumpHeap(outputFile, live); + } +} + /* * The following substitutions replace methods where reflection is used in the Graal code. */