Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions compiler/docs/ReplayCompilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,32 +101,51 @@ private static Map<String, Long> wordCount(List<String> sentences) {
return sentences.stream().flatMap(sentence -> Arrays.stream(sentence.split("\\s+"))).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

private static List<String> reverseAndUppercase(List<String> 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<DebugOptions.OptimizationLogTarget> 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);
});
}

Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading