From a6548018eeb5f6dd8109d4cee05a8e77b591332d Mon Sep 17 00:00:00 2001 From: Andrej Pecimuth Date: Mon, 10 Nov 2025 15:55:40 +0100 Subject: [PATCH] Serialize system properties and thrown exceptions for recorded compilations. --- compiler/docs/ReplayCompilation.md | 17 +++ .../RecordedOperationPersistenceTest.java | 5 +- .../test/ReplayCompilationTest.java | 45 ++++-- .../compiler/hotspot/CompilationTask.java | 3 +- .../replaycomp/CompilationTaskProduct.java | 100 +++++++++++++ .../RecordedOperationPersistence.java | 89 +++++++++++- .../replaycomp/ReplayCompilationRunner.java | 134 +++++++++++++----- .../replaycomp/ReplayCompilationSupport.java | 53 +++---- 8 files changed, 366 insertions(+), 80 deletions(-) create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/CompilationTaskProduct.java diff --git a/compiler/docs/ReplayCompilation.md b/compiler/docs/ReplayCompilation.md index b202f2b1f875..6b27ed8b3d3a 100644 --- a/compiler/docs/ReplayCompilation.md +++ b/compiler/docs/ReplayCompilation.md @@ -51,6 +51,14 @@ mx benchmark renaissance:scrabble -- -Djdk.graal.CompilationFailureAction=Diagno It is possible to **not** record retried compilations with the option `-Djdk.graal.DiagnoseOptions=RecordForReplay=`. +When a recorded compilation ends with an exception, the type and stack trace of the exception is saved in the replay +file. During replay, the launcher verifies that the replayed compilation throws an exception of the same type. Use the +`--verbose=true` option to print the stack trace of the recorded exception. + +```shell +mx replaycomp --verbose=true ./replay-files +``` + ## Replay with JVM Arguments JVM arguments, including compiler options, can be passed directly to the `replaycomp` command. @@ -84,6 +92,15 @@ mx replaycomp --jdk-home $GRAALVM_HOME ./replay-files ## Replay Options +`--verbose=true` prints additional information for every compilation, including: +* the system properties of the recorded compilation (the options include the VM command from the recorded run), +* the final canonical graph of the recorded/replayed compilation, +* the stack trace of the exception thrown during the recorded/replayed compilation. + +```shell +mx replaycomp --verbose=true ./replay-files +``` + `--compare-graphs=true` compares the final canonical graph of the replayed compilation to the recorded one, which is included in the JSON replay file. If there is a mismatch, the command exits with a non-zero status. diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/RecordedOperationPersistenceTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/RecordedOperationPersistenceTest.java index 2c4ae5c65394..4fcf9364b3ff 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/RecordedOperationPersistenceTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/RecordedOperationPersistenceTest.java @@ -40,6 +40,7 @@ import jdk.graal.compiler.core.test.GraalCompilerTest; import jdk.graal.compiler.hotspot.Platform; import jdk.graal.compiler.hotspot.replaycomp.CompilationProxyMapper; +import jdk.graal.compiler.hotspot.replaycomp.CompilationTaskProduct; import jdk.graal.compiler.hotspot.replaycomp.CompilerInterfaceDeclarations; import jdk.graal.compiler.hotspot.replaycomp.OperationRecorder; import jdk.graal.compiler.hotspot.replaycomp.RecordedForeignCallLinkages; @@ -48,6 +49,7 @@ import jdk.graal.compiler.hotspot.replaycomp.proxy.CompilationProxy; import jdk.graal.compiler.hotspot.replaycomp.proxy.CompilationProxyBase; import jdk.graal.compiler.util.CollectionsUtil; +import jdk.graal.compiler.util.EconomicHashMap; import jdk.graal.compiler.util.json.JsonWriter; import jdk.vm.ci.hotspot.HotSpotCompilationRequest; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; @@ -141,8 +143,9 @@ private RecordedOperationPersistence.RecordedCompilationUnit createRecordedCompi "test configuration", false, Platform.ofCurrentHost(), + new EconomicHashMap<>(), new RecordedForeignCallLinkages(EconomicMap.create()), - "test graph", + new CompilationTaskProduct.CompilationTaskException("test exception", "test stack trace"), operations); } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java index cbbdd845df95..6571a9c4c55c 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java @@ -101,32 +101,51 @@ private static Map wordCount(List sentences) { return sentences.stream().flatMap(sentence -> Arrays.stream(sentence.split("\\s+"))).collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); } + private static List reverseAndUppercase(List input) { + return input.stream().map(s -> new StringBuilder(s).reverse().toString().toUpperCase()).collect(Collectors.toList()); + } + @Test public void recordsOnRetryAndReplays() throws Throwable { lengthsSquared(List.of("foo", "bar", "baz")); runTest((temp) -> { String methodName = "lengthsSquared"; ResolvedJavaMethod method = getResolvedJavaMethod(methodName); - OptionValues initialOptions = getInitialOptions(); + OptionValues crashOptions = new OptionValues(getInitialOptions(), GraalCompilerOptions.CrashAt, methodName); String diagnoseOptionValue = DebugOptions.RecordForReplay.getName() + "=" + methodName; - OptionValues crashAndDiagnoseOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(), + OptionValues crashAndDiagnoseOptions = new OptionValues(crashOptions, DebugOptions.DumpPath, temp.toString(), GraalCompilerOptions.CompilationFailureAction, CompilationWrapper.ExceptionAction.Diagnose, - DebugOptions.DiagnoseOptions, diagnoseOptionValue, GraalCompilerOptions.CrashAt, methodName); - /* - * Run a regular compilation with a forced crash, then retry and record the compilation. - * We need to run in a new compiler instance to override the dump path for diagnostics, - * where the recorded compilation unit is saved. - */ + DebugOptions.DiagnoseOptions, diagnoseOptionValue); + // Run a regular compilation with a forced crash. HotSpotCompilationRequestResult regularResult = runRegularCompilation(method, crashAndDiagnoseOptions); assertTrue(regularResult.getFailure() != null); + // Replay and check that the compilation task ends with the same exception. + replayCompilation(findReplayCompFile(temp.path), crashOptions, false); + }); + } - // Replay the compilation without forcing a crash and enable diagnostic options. + @Test + public void recordsAndReplaysWithDiagnosticOptions() throws Throwable { + reverseAndUppercase(List.of("foo", "bar", "baz")); + runTest((temp) -> { + ResolvedJavaMethod method = getResolvedJavaMethod("reverseAndUppercase"); + OptionValues initialOptions = getInitialOptions(); + OptionValues recordOptions = new OptionValues(initialOptions, DebugOptions.RecordForReplay, "*", + DebugOptions.DumpPath, temp.toString()); + runRegularCompilation(method, recordOptions); + // Replay with the same options and verify the graphs are equal. + Path replayFile = findReplayCompFile(temp.path); + replayCompilation(replayFile, initialOptions, true); + /* + * Replay with diagnostic options. Do not check graph equality, since enabling graph + * dumps typically changes the graphs. + */ EconomicSet logTargets = EconomicSet.create(); logTargets.add(DebugOptions.OptimizationLogTarget.Stdout); - OptionValues replayOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(), + OptionValues diagnosticOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(), DebugOptions.PrintGraph, DebugOptions.PrintGraphTarget.File, DebugOptions.Dump, ":1", DebugOptions.OptimizationLog, logTargets, DebugOptions.Log, "", DebugOptions.PrintBackendCFG, true); - replayCompilation(findReplayCompFile(temp.path), replayOptions); + replayCompilation(replayFile, diagnosticOptions, false); }); } @@ -204,14 +223,14 @@ private static HotSpotCompilationRequestResult runRegularCompilation(ResolvedJav return task.runCompilation(options); } - private static void replayCompilation(Path replayCompFile, OptionValues options) throws ReplayCompilationRunner.ReplayLauncherFailure { + private static void replayCompilation(Path replayCompFile, OptionValues options, boolean compareGraphs) throws ReplayCompilationRunner.ReplayLauncherFailure { CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build(); HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); RuntimeProvider runtimeProvider = Graal.getRequiredCapability(RuntimeProvider.class); CompilerConfigurationFactory configFactory = CompilerConfigurationFactory.selectFactory(runtimeProvider.getCompilerConfigurationName(), options, jvmciRuntime); try (ReplayCompilationRunner.Reproducer reproducer = ReplayCompilationRunner.Reproducer.initializeFromFile(replayCompFile.toString(), declarations, jvmciRuntime, options, configFactory, new GlobalMetrics(), TTY.out().out(), EconomicMap.create())) { - reproducer.compile().verify(false); + reproducer.compile().compareCompilationProducts(compareGraphs); } } 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 097f62c7793f..83e0addb0ba8 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 @@ -408,13 +408,14 @@ private HotSpotCompilationRequestResult performCompilationWithReplaySupport(Debu performRecompilationCheck(options, method); CompilationReplayBytecodes.add(debug, result.getBytecodeSize()); } catch (Throwable e) { + replaySupport.recordCompilationTaskException(e); throw debug.handle(e); } try (DebugCloseable b = CodeInstallationTime.start(debug)) { installMethod(selectedCompiler.getGraalRuntime().getHostBackend(), debug, graph, result); } printer.finish(result, installedCode); - replaySupport.recordCompilationArtifacts(graph, result); + replaySupport.recordCompilationTaskArtifacts(graph, result); return buildCompilationRequestResult(method); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/CompilationTaskProduct.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/CompilationTaskProduct.java new file mode 100644 index 000000000000..159370eb1cc0 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/CompilationTaskProduct.java @@ -0,0 +1,100 @@ +/* + * 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.hotspot.replaycomp; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.printer.CanonicalStringGraphPrinter; + +/** + * The product of a recording or replay compilation task, which may be a successfully compiled graph + * or an exception. + */ +public sealed interface CompilationTaskProduct { + /** + * Returns {@code true} if the product represents a successfully completed compilation task. + */ + boolean isSuccess(); + + /** + * The product of a compilation task that failed with an exception. + * + * @param className the class name of the exception + * @param stackTrace the stack trace of the exception + */ + record CompilationTaskException(String className, String stackTrace) implements CompilationTaskProduct { + /** + * Represents an unknown exception recorded by an earlier compiler version, which did not + * record this information. This is temporarily needed for compatibility with older replay + * files. + */ + public static final CompilationTaskException UNKNOWN = new CompilationTaskException("unknown", "unknown"); + + @Override + public boolean isSuccess() { + return false; + } + } + + /** + * The artifacts produced by a successful compilation task on this VM. + * + * @param graph the final graph + * @param result the compilation result + */ + record CompilationTaskArtifacts(StructuredGraph graph, CompilationResult result) implements CompilationTaskProduct { + /** + * Returns the canonical graph string for the final graph. + */ + public String finalCanonicalGraph() { + return CanonicalStringGraphPrinter.getCanonicalGraphString(graph, false, true); + } + + /** + * Converts the object to a serializable representation that can be loaded during replay. + */ + public RecordedCompilationTaskArtifacts asRecordedArtifacts() { + return new RecordedCompilationTaskArtifacts(finalCanonicalGraph()); + } + + @Override + public boolean isSuccess() { + return true; + } + } + + /** + * The final graph produced by a successful compilation task. This is a serializable subset of + * {@link CompilationTaskArtifacts} used for recorded compilation tasks. + * + * @param finalGraph the final canonical graph + */ + record RecordedCompilationTaskArtifacts(String finalGraph) implements CompilationTaskProduct { + @Override + public boolean isSuccess() { + return true; + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/RecordedOperationPersistence.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/RecordedOperationPersistence.java index 71e6ea20d60e..0708ecf79182 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/RecordedOperationPersistence.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/RecordedOperationPersistence.java @@ -117,13 +117,14 @@ public class RecordedOperationPersistence { * @param compilerConfiguration the name of the compiler configuration * @param isLibgraal {@code true} if the recorded compilation executed on libgraal * @param platform the platform of the system + * @param properties the system property map * @param linkages the recorded foreign call linkages - * @param finalGraph the final canonical graph or {@code null} if not available + * @param product the product of the recorded compilation task * @param operations the recorded operations and their results */ public record RecordedCompilationUnit(HotSpotCompilationRequest request, String compilerConfiguration, - boolean isLibgraal, Platform platform, RecordedForeignCallLinkages linkages, String finalGraph, - List operations) { + boolean isLibgraal, Platform platform, Map properties, RecordedForeignCallLinkages linkages, + CompilationTaskProduct product, List operations) { } /** @@ -1438,6 +1439,7 @@ public void serialize(Object instance, JsonBuilder.ObjectBuilder objectBuilder, objectBuilder.append("archName", unit.platform.archName()); objectBuilder.append("compilerConfiguration", unit.compilerConfiguration); objectBuilder.append("isLibgraal", unit.isLibgraal); + objectBuilder.append("properties", unit.properties); objectBuilder.append("entryBCI", unit.request.getEntryBCI()); objectBuilder.append("compileId", unit.request.getId()); try (JsonBuilder.ArrayBuilder arrayBuilder = objectBuilder.append("operations").array()) { @@ -1446,7 +1448,11 @@ public void serialize(Object instance, JsonBuilder.ObjectBuilder objectBuilder, } } serializer.serialize(unit.linkages, objectBuilder.append("linkages"), RecordedForeignCallLinkagesSerializer.TAG); - objectBuilder.append("finalGraph", unit.finalGraph); + CompilationTaskProduct product = unit.product; + if (product instanceof CompilationTaskProduct.CompilationTaskArtifacts artifacts) { + product = artifacts.asRecordedArtifacts(); + } + serializer.serialize(product, objectBuilder.append("product")); } @SuppressWarnings("unchecked") @@ -1458,6 +1464,14 @@ public Object deserialize(EconomicMap json, RecursiveDeserialize Platform platform = new Platform(osName, archName); String compilerConfiguration = (String) json.get("compilerConfiguration"); boolean isLibgraal = (boolean) json.get("isLibgraal"); + EconomicMap propertyMap = ((EconomicMap) json.get("properties")); + Map properties = new EconomicHashMap<>(); + if (propertyMap != null) { + var propertyCursor = ((EconomicMap) json.get("properties")).getEntries(); + while (propertyCursor.advance()) { + properties.put(propertyCursor.getKey(), (String) propertyCursor.getValue()); + } + } int entryBCI = (int) json.get("entryBCI"); int compileId = (int) json.get("compileId"); List list = (List) json.get("operations"); @@ -1466,8 +1480,19 @@ public Object deserialize(EconomicMap json, RecursiveDeserialize operations.add((OperationRecorder.RecordedOperation) deserializer.deserialize(object, proxyFactory, OperationSerializer.TAG)); } RecordedForeignCallLinkages linkages = (RecordedForeignCallLinkages) deserializer.deserialize(json.get("linkages"), proxyFactory, RecordedForeignCallLinkagesSerializer.TAG); - String finalGraph = (String) json.get("finalGraph"); - return new RecordedCompilationUnit(new HotSpotCompilationRequest(method, entryBCI, 0, compileId), compilerConfiguration, isLibgraal, platform, linkages, finalGraph, operations); + CompilationTaskProduct product; + if (json.containsKey("finalGraph")) { + // Compatibility with replay files from a previous compiler version. + String finalGraph = (String) json.get("finalGraph"); + if (finalGraph == null) { + product = CompilationTaskProduct.CompilationTaskException.UNKNOWN; + } else { + product = new CompilationTaskProduct.RecordedCompilationTaskArtifacts(finalGraph); + } + } else { + product = (CompilationTaskProduct) deserializer.deserialize(json.get("product"), proxyFactory); + } + return new RecordedCompilationUnit(new HotSpotCompilationRequest(method, entryBCI, 0, compileId), compilerConfiguration, isLibgraal, platform, properties, linkages, product, operations); } } @@ -1838,6 +1863,56 @@ public Object deserialize(EconomicMap json, RecursiveDeserialize } } + private static final class CompilationTaskExceptionSerializer implements ObjectSerializer { + @Override + public Class clazz() { + return CompilationTaskProduct.CompilationTaskException.class; + } + + @Override + public String tag() { + return "taskException"; + } + + @Override + public void serialize(Object instance, JsonBuilder.ObjectBuilder objectBuilder, RecursiveSerializer serializer) throws IOException { + var product = (CompilationTaskProduct.CompilationTaskException) instance; + objectBuilder.append("className", product.className()); + objectBuilder.append("stackTrace", product.stackTrace()); + } + + @Override + public Object deserialize(EconomicMap json, RecursiveDeserializer deserializer, ProxyFactory proxyFactory) { + String className = (String) json.get("className"); + String stackTrace = (String) json.get("stackTrace"); + return new CompilationTaskProduct.CompilationTaskException(className, stackTrace); + } + } + + private static final class RecordedCompilationTaskArtifactsSerializer implements ObjectSerializer { + @Override + public Class clazz() { + return CompilationTaskProduct.RecordedCompilationTaskArtifacts.class; + } + + @Override + public String tag() { + return "taskArtifacts"; + } + + @Override + public void serialize(Object instance, JsonBuilder.ObjectBuilder objectBuilder, RecursiveSerializer serializer) throws IOException { + var product = (CompilationTaskProduct.RecordedCompilationTaskArtifacts) instance; + objectBuilder.append("finalGraph", product.finalGraph()); + } + + @Override + public Object deserialize(EconomicMap json, RecursiveDeserializer deserializer, ProxyFactory proxyFactory) { + String finalGraph = (String) json.get("finalGraph"); + return new CompilationTaskProduct.RecordedCompilationTaskArtifacts(finalGraph); + } + } + private final EconomicMap tagSerializers; private final EconomicMap, ObjectSerializer> exactClassSerializers; @@ -1907,6 +1982,8 @@ public RecordedOperationPersistence(CompilerInterfaceDeclarations declarations, addSerializer(new RegisterConfigSerializer(hostPlatform, hostTarget)); addSerializer(new ExceptionHandlerSerializer()); addSerializer(new ThrowableSerializer()); + addSerializer(new CompilationTaskExceptionSerializer()); + addSerializer(new RecordedCompilationTaskArtifactsSerializer()); } private void addSerializer(ObjectSerializer serializer) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java index de261c8bcf5b..01c5c5260631 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java @@ -41,7 +41,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Objects; +import java.util.Map; import org.graalvm.collections.EconomicMap; @@ -124,6 +124,7 @@ public int getStatus() { @SuppressWarnings("try") public static ExitStatus run(String[] args, PrintStream out) { Program program = new Program("mx replaycomp", "Replay compilations from files."); + OptionValue verboseArg = program.addNamed("--verbose", new BooleanValue("true|false", false, "Increase the verbosity of the output.")); OptionValue compareGraphsArg = program.addNamed("--compare-graphs", new BooleanValue("true|false", false, "Verify that the replayed graph equals the recorded one.")); OptionValue inputPathArg = program.addPositional(new StringValue("TARGET", "Path to a directory with replay compilation files (or path to a single file).")); CommandGroup commandGroup = new CommandGroup<>("COMMAND", new ReplayCommand(), "The mode to replay in."); @@ -134,7 +135,7 @@ public static ExitStatus run(String[] args, PrintStream out) { if (inputFiles == null) { return ExitStatus.Failure; } - return commandGroup.getSelectedCommand().run(out, inputFiles, compareGraphsArg.getValue()); + return commandGroup.getSelectedCommand().run(out, inputFiles, verboseArg.getValue(), compareGraphsArg.getValue()); } /** @@ -156,23 +157,28 @@ private LauncherCommand(String name, String description) { * * @param out the stream for output * @param inputFiles the files that should be replayed + * @param verbose increase the verbosity of the output * @param compareGraphs whether the replayed graph should be compared with the recorded one * @return the exit status of the launcher */ - public abstract ExitStatus run(PrintStream out, List inputFiles, boolean compareGraphs); + public abstract ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs); } /** * A command that replays a set of files (e.g., for debugging purposes). */ private static final class ReplayCommand extends LauncherCommand { + private static final String RECORDED = "Recorded"; + + private static final String REPLAY = "Replay"; + private ReplayCommand() { super("--replay", "Replay compilations."); } @SuppressWarnings("try") @Override - public ExitStatus run(PrintStream out, List inputFiles, boolean compareGraphs) { + public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs) { OptionValues systemOptions = new OptionValues(HotSpotGraalOptionValues.parseOptions()); OptionValues options = new OptionValues(systemOptions, GraalCompilerOptions.SystemicCompilationFailureRate, 0); CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build(); @@ -186,13 +192,26 @@ public ExitStatus run(PrintStream out, List inputFiles, boolean compareGra try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null; Reproducer reproducer = Reproducer.initializeFromFile(file.toString(), declarations, runtime, options, factory, globalMetrics, out, EconomicMap.create())) { - reproducer.compile().verify(compareGraphs); + if (verbose) { + printProperties(reproducer.properties, file, out); + printException(reproducer.request, reproducer.product, RECORDED, out); + printGraph(reproducer.request, reproducer.product, RECORDED, out); + } + if (reproducer.product instanceof CompilationTaskProduct.CompilationTaskException recordedException) { + out.printf("Expecting the replay of %s to throw %s%n", reproducer.request, recordedException.className()); + } + ReplayResult result = reproducer.compile(); + if (verbose) { + printException(reproducer.request, result.replayedProduct, REPLAY, out); + printGraph(reproducer.request, result.replayedProduct, REPLAY, out); + } + result.compareCompilationProducts(compareGraphs); out.println("Successfully replayed " + reproducer.request); } catch (ReplayParserFailure failure) { out.println("Replay failed: " + failure.getMessage()); task.setFailureReason(failure.getMessage()); } catch (Exception e) { - out.println("Replay failed: " + e); + out.printf("Replay of %s failed with the below exception:%n", file); e.printStackTrace(out); return ExitStatus.Failure; } @@ -202,6 +221,35 @@ public ExitStatus run(PrintStream out, List inputFiles, boolean compareGra globalMetrics.print(options); return ExitStatus.Success; } + + private static void printProperties(Map properties, Path file, PrintStream out) { + if (properties == null) { + return; + } + out.printf("System properties of the recording VM from %s:%n", file); + for (Map.Entry entry : properties.entrySet()) { + out.printf(" %s=%s%n", entry.getKey(), entry.getValue()); + } + out.println(); + } + + private static void printException(HotSpotCompilationRequest request, CompilationTaskProduct product, String compilationKind, PrintStream out) { + if (product instanceof CompilationTaskProduct.CompilationTaskException taskException) { + out.printf("%s compilation of %s ended with the below exception:%n%s%n", compilationKind, request, taskException.stackTrace()); + } + } + + private static void printGraph(HotSpotCompilationRequest request, CompilationTaskProduct product, String compilationKind, PrintStream out) { + String finalGraph; + if (product instanceof CompilationTaskProduct.CompilationTaskArtifacts artifacts) { + finalGraph = artifacts.finalCanonicalGraph(); + } else if (product instanceof CompilationTaskProduct.RecordedCompilationTaskArtifacts artifacts) { + finalGraph = artifacts.finalGraph(); + } else { + return; + } + out.printf("%s compilation of %s completed with the below final canonical graph:%n%s%n", compilationKind, request, finalGraph); + } } private static final double ONE_MILLION = 1_000_000d; @@ -222,7 +270,7 @@ private BenchmarkCommand() { @SuppressWarnings("try") @Override - public ExitStatus run(PrintStream out, List inputFiles, boolean compareGraphs) { + public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs) { OptionValues options = new OptionValues(HotSpotGraalOptionValues.parseOptions()); CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build(); HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime(); @@ -233,8 +281,13 @@ public ExitStatus run(PrintStream out, List inputFiles, boolean compareGra EconomicMap internPool = EconomicMap.create(); for (Path file : inputFiles) { try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) { - reproducers.add(Reproducer.initializeFromFile(file.toString(), declarations, runtime, options, - factory, globalMetrics, out, internPool)); + Reproducer reproducer = Reproducer.initializeFromFile(file.toString(), declarations, runtime, options, + factory, globalMetrics, out, internPool); + if (reproducer.product instanceof CompilationTaskProduct.CompilationTaskException recordedException) { + out.printf("The compilation in %s recorded an exception of type %s; only successful compilations are suitable for replay benchmarks%n", file, recordedException.className()); + return ExitStatus.Failure; + } + reproducers.add(reproducer); } catch (ReplayParserFailure failure) { out.printf("Preparation failed for %s: %s%n", file, failure.getMessage()); return ExitStatus.Failure; @@ -257,7 +310,7 @@ public ExitStatus run(PrintStream out, List inputFiles, boolean compareGra for (Reproducer reproducer : reproducers) { try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) { ReplayResult replayResult = reproducer.compile(); - replayResult.verify(compareGraphs); + replayResult.compareCompilationProducts(compareGraphs); metrics.addVerifiedResult(replayResult); } catch (Exception e) { out.println("Replay failed: " + e); @@ -367,22 +420,31 @@ private static final class ReplayParserFailure extends ReplayLauncherFailure { * * @param request the compilation request for the replay compilation * @param result the result of the replayed compilation - * @param replayedArtifacts the artifacts produced by the replayed compilation - * @param originalGraph the canonical graph string of the final original graph (if available) + * @param recordedProduct the product of the recorded compilation + * @param replayedProduct the product of the replayed compilation */ - public record ReplayResult(CompilationRequest request, CompilationRequestResult result, ReplayCompilationSupport.CompilationArtifacts replayedArtifacts, String originalGraph) { - public void verify(boolean verifyGraphs) throws ReplayLauncherFailure { - if (result.getFailure() == null) { - if (!verifyGraphs) { - return; - } - if (originalGraph == null) { - throw new ReplayLauncherFailure("Cannot verify the replayed graph for " + request); - } else if (!originalGraph.equals(replayedArtifacts.finalCanonicalGraph())) { - throw new ReplayLauncherFailure("Replay completed but final graphs differ"); - } - } else { - throw new ReplayLauncherFailure(Objects.toString(result.getFailure())); + public record ReplayResult(CompilationRequest request, CompilationRequestResult result, CompilationTaskProduct recordedProduct, CompilationTaskProduct replayedProduct) { + /** + * Verifies that the replayed compilation product matches the recorded one. + * + * @param compareGraphs whether the final graphs should be compared + * @throws ReplayLauncherFailure if the replayed compilation task product does not match the + * recorded one + */ + public void compareCompilationProducts(boolean compareGraphs) throws ReplayLauncherFailure { + if (recordedProduct instanceof CompilationTaskProduct.CompilationTaskException recordedException && + replayedProduct instanceof CompilationTaskProduct.CompilationTaskException replayedException && + !recordedException.className().equals(replayedException.className()) && + recordedException != CompilationTaskProduct.CompilationTaskException.UNKNOWN) { + throw new ReplayLauncherFailure("Replay compilation ended with a different exception than the recorded one"); + } else if (compareGraphs && recordedProduct instanceof CompilationTaskProduct.RecordedCompilationTaskArtifacts(String finalGraph) && + replayedProduct instanceof CompilationTaskProduct.CompilationTaskArtifacts replayedArtifacts && + !finalGraph.equals(replayedArtifacts.finalCanonicalGraph())) { + throw new ReplayLauncherFailure("Replay compilation completed successfully but the final graphs differ"); + } else if (recordedProduct.isSuccess() && !replayedProduct.isSuccess()) { + throw new ReplayLauncherFailure("Recorded compilation completed successfully but the replayed one ended with an exception"); + } else if (!recordedProduct.isSuccess() && replayedProduct.isSuccess()) { + throw new ReplayLauncherFailure("Recorded compilation ended with an exception but the replayed one completed successfully"); } } } @@ -402,19 +464,25 @@ public static final class Reproducer implements Closeable { private final HotSpotCompilationRequest request; /** - * The final graph of the recorded compilation. + * The product of the recorded compilation task. + */ + private final CompilationTaskProduct product; + + /** + * The system properties from the recording run. */ - private final String finalGraph; + private final Map properties; /** * The compiler options for replay. */ private final OptionValues options; - private Reproducer(HotSpotGraalCompiler replayCompiler, HotSpotCompilationRequest request, String finalGraph, OptionValues options) { + private Reproducer(HotSpotGraalCompiler replayCompiler, HotSpotCompilationRequest request, CompilationTaskProduct product, Map properties, OptionValues options) { this.replayCompiler = replayCompiler; this.request = request; - this.finalGraph = finalGraph; + this.product = product; + this.properties = properties; this.options = options; } @@ -467,7 +535,7 @@ public static Reproducer initializeFromFile(String fileName, CompilerInterfaceDe " but the initialized compiler is " + graalRuntime.getCompilerConfigurationName())); } graalRuntime.getReplayCompilationSupport().setRecordedForeignCallLinkages(compilationUnit.linkages()); - return new Reproducer(replayCompiler, request, compilationUnit.finalGraph(), options); + return new Reproducer(replayCompiler, request, compilationUnit.product(), compilationUnit.properties(), options); } /** @@ -479,8 +547,8 @@ public static Reproducer initializeFromFile(String fileName, CompilerInterfaceDe public ReplayResult compile() { ReplayCompilationSupport support = replayCompiler.getGraalRuntime().getReplayCompilationSupport(); CompilationRequestResult result = replayCompiler.compileMethod(request, true, options); - ReplayCompilationSupport.CompilationArtifacts replayedArtifacts = support.clearCompilationArtifacts(); - return new ReplayResult(request, result, replayedArtifacts, finalGraph); + CompilationTaskProduct replayProduct = support.clearCompilationTaskProduct(); + return new ReplayResult(request, result, product, replayProduct); } /** @@ -593,7 +661,7 @@ public void beginIteration(PrintStream out, PrintStream outStat) { } public void addVerifiedResult(ReplayResult replayResult) { - CompilationResult result = replayResult.replayedArtifacts().result(); + CompilationResult result = ((CompilationTaskProduct.CompilationTaskArtifacts) replayResult.replayedProduct()).result(); compiledBytecodes += result.getBytecodeSize(); targetCodeSize += result.getTargetCodeSize(); targetCodeHash = targetCodeHash * 31 + Arrays.hashCode(result.getTargetCode()); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationSupport.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationSupport.java index ae3fa84b0a8f..3c85d6c23aad 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationSupport.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationSupport.java @@ -24,6 +24,9 @@ */ package jdk.graal.compiler.hotspot.replaycomp; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.file.Path; import java.util.function.Predicate; @@ -34,6 +37,7 @@ import jdk.graal.compiler.debug.DebugCloseable; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.DebugOptions; +import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.debug.MethodFilter; import jdk.graal.compiler.debug.PathUtilities; import jdk.graal.compiler.debug.TTY; @@ -45,8 +49,8 @@ import jdk.graal.compiler.hotspot.meta.HotSpotProviders; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.printer.CanonicalStringGraphPrinter; import jdk.graal.compiler.replacements.SnippetTemplate; +import jdk.graal.compiler.serviceprovider.GraalServices; import jdk.graal.compiler.util.json.JsonWriter; import jdk.vm.ci.hotspot.HotSpotCompilationRequest; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; @@ -136,9 +140,9 @@ public static boolean matchesRecordCompilationFilter(OptionValues options, Resol private RecordedForeignCallLinkages recordedForeignCallLinkages; /** - * The compilation artifacts of the current compiler thread. + * The product of the last compilation task. */ - private final ThreadLocal compilationArtifacts = ThreadLocal.withInitial(() -> null); + private final ThreadLocal compilationProduct = ThreadLocal.withInitial(() -> null); /** * The compiler's configuration name. @@ -275,10 +279,9 @@ private void serializeRecordedCompilation(HotSpotCompilationRequest originalRequ RecordedOperationPersistence persistence = new RecordedOperationPersistence(proxies.getDeclarations(), Platform.ofCurrentHost(), HotSpotJVMCIRuntime.runtime().getHostJVMCIBackend().getTarget()); RecordingCompilationProxies recordingCompilationProxies = (RecordingCompilationProxies) proxies; - CompilationArtifacts artifacts = clearCompilationArtifacts(); - String finalCanonicalGraph = (artifacts == null) ? null : artifacts.finalCanonicalGraph(); + CompilationTaskProduct product = clearCompilationTaskProduct(); RecordedOperationPersistence.RecordedCompilationUnit compilationUnit = new RecordedOperationPersistence.RecordedCompilationUnit(originalRequest, compilerConfigurationName, - LibGraalSupport.inLibGraalRuntime(), recordingCompilationProxies.targetPlatform(), linkages, finalCanonicalGraph, + LibGraalSupport.inLibGraalRuntime(), recordingCompilationProxies.targetPlatform(), GraalServices.getSavedProperties(), linkages, product, recordingCompilationProxies.collectOperationsForSerialization()); try (JsonWriter jsonWriter = new JsonWriter(path)) { persistence.dump(compilationUnit, jsonWriter); @@ -378,41 +381,39 @@ public Platform targetPlatform() { } /** - * The artifacts produced by a compilation. + * Records the exception thrown by the last compilation task of the current compiler thread. * - * @param graph the final graph - * @param result the compilation result + * @param e the thrown exception */ - public record CompilationArtifacts(StructuredGraph graph, CompilationResult result) { - /** - * Returns the canonical graph string for the final graph. - */ - public String finalCanonicalGraph() { - return CanonicalStringGraphPrinter.getCanonicalGraphString(graph, false, true); + public void recordCompilationTaskException(Throwable e) { + try (StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter)) { + e.printStackTrace(out); + compilationProduct.set(new CompilationTaskProduct.CompilationTaskException(e.getClass().getName(), stringWriter.toString())); + } catch (IOException ex) { + throw new GraalError(ex); } } /** - * Records the artifacts produced by the last compilation of the current compiler thread. + * Records the artifacts produced by a successful compilation task of the current compiler + * thread. * * @param graph the final graph * @param result the compilation result */ - public void recordCompilationArtifacts(StructuredGraph graph, CompilationResult result) { - compilationArtifacts.set(new CompilationArtifacts(graph, result)); + public void recordCompilationTaskArtifacts(StructuredGraph graph, CompilationResult result) { + compilationProduct.set(new CompilationTaskProduct.CompilationTaskArtifacts(graph, result)); } /** - * Clears and returns the artifacts produced by the last successfully completed compilation of - * the current compiler thread. May return {@code null} if the last compilation did not complete - * successfully. + * Clears and returns the product of the last compilation task of the current compiler thread. * - * @return the cleared compilation artifacts or {@code null} + * @return the cleared product of the last compilation task */ - public CompilationArtifacts clearCompilationArtifacts() { - CompilationArtifacts result = compilationArtifacts.get(); - compilationArtifacts.remove(); - return result; + public CompilationTaskProduct clearCompilationTaskProduct() { + CompilationTaskProduct product = compilationProduct.get(); + compilationProduct.remove(); + return product; } /**