Skip to content

Commit 5d31f31

Browse files
committed
[GR-68907] Serialize system properties and thrown exceptions for recorded compilations.
PullRequest: graal/22594
2 parents 30abf01 + a654801 commit 5d31f31

File tree

8 files changed

+366
-80
lines changed

8 files changed

+366
-80
lines changed

compiler/docs/ReplayCompilation.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ mx benchmark renaissance:scrabble -- -Djdk.graal.CompilationFailureAction=Diagno
5151

5252
It is possible to **not** record retried compilations with the option `-Djdk.graal.DiagnoseOptions=RecordForReplay=`.
5353

54+
When a recorded compilation ends with an exception, the type and stack trace of the exception is saved in the replay
55+
file. During replay, the launcher verifies that the replayed compilation throws an exception of the same type. Use the
56+
`--verbose=true` option to print the stack trace of the recorded exception.
57+
58+
```shell
59+
mx replaycomp --verbose=true ./replay-files
60+
```
61+
5462
## Replay with JVM Arguments
5563

5664
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
8492

8593
## Replay Options
8694

95+
`--verbose=true` prints additional information for every compilation, including:
96+
* the system properties of the recorded compilation (the options include the VM command from the recorded run),
97+
* the final canonical graph of the recorded/replayed compilation,
98+
* the stack trace of the exception thrown during the recorded/replayed compilation.
99+
100+
```shell
101+
mx replaycomp --verbose=true ./replay-files
102+
```
103+
87104
`--compare-graphs=true` compares the final canonical graph of the replayed compilation to the recorded one, which is
88105
included in the JSON replay file. If there is a mismatch, the command exits with a non-zero status.
89106

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/RecordedOperationPersistenceTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import jdk.graal.compiler.core.test.GraalCompilerTest;
4141
import jdk.graal.compiler.hotspot.Platform;
4242
import jdk.graal.compiler.hotspot.replaycomp.CompilationProxyMapper;
43+
import jdk.graal.compiler.hotspot.replaycomp.CompilationTaskProduct;
4344
import jdk.graal.compiler.hotspot.replaycomp.CompilerInterfaceDeclarations;
4445
import jdk.graal.compiler.hotspot.replaycomp.OperationRecorder;
4546
import jdk.graal.compiler.hotspot.replaycomp.RecordedForeignCallLinkages;
@@ -48,6 +49,7 @@
4849
import jdk.graal.compiler.hotspot.replaycomp.proxy.CompilationProxy;
4950
import jdk.graal.compiler.hotspot.replaycomp.proxy.CompilationProxyBase;
5051
import jdk.graal.compiler.util.CollectionsUtil;
52+
import jdk.graal.compiler.util.EconomicHashMap;
5153
import jdk.graal.compiler.util.json.JsonWriter;
5254
import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
5355
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
@@ -141,8 +143,9 @@ private RecordedOperationPersistence.RecordedCompilationUnit createRecordedCompi
141143
"test configuration",
142144
false,
143145
Platform.ofCurrentHost(),
146+
new EconomicHashMap<>(),
144147
new RecordedForeignCallLinkages(EconomicMap.create()),
145-
"test graph",
148+
new CompilationTaskProduct.CompilationTaskException("test exception", "test stack trace"),
146149
operations);
147150
}
148151

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,32 +101,51 @@ private static Map<String, Long> wordCount(List<String> sentences) {
101101
return sentences.stream().flatMap(sentence -> Arrays.stream(sentence.split("\\s+"))).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
102102
}
103103

104+
private static List<String> reverseAndUppercase(List<String> input) {
105+
return input.stream().map(s -> new StringBuilder(s).reverse().toString().toUpperCase()).collect(Collectors.toList());
106+
}
107+
104108
@Test
105109
public void recordsOnRetryAndReplays() throws Throwable {
106110
lengthsSquared(List.of("foo", "bar", "baz"));
107111
runTest((temp) -> {
108112
String methodName = "lengthsSquared";
109113
ResolvedJavaMethod method = getResolvedJavaMethod(methodName);
110-
OptionValues initialOptions = getInitialOptions();
114+
OptionValues crashOptions = new OptionValues(getInitialOptions(), GraalCompilerOptions.CrashAt, methodName);
111115
String diagnoseOptionValue = DebugOptions.RecordForReplay.getName() + "=" + methodName;
112-
OptionValues crashAndDiagnoseOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(),
116+
OptionValues crashAndDiagnoseOptions = new OptionValues(crashOptions, DebugOptions.DumpPath, temp.toString(),
113117
GraalCompilerOptions.CompilationFailureAction, CompilationWrapper.ExceptionAction.Diagnose,
114-
DebugOptions.DiagnoseOptions, diagnoseOptionValue, GraalCompilerOptions.CrashAt, methodName);
115-
/*
116-
* Run a regular compilation with a forced crash, then retry and record the compilation.
117-
* We need to run in a new compiler instance to override the dump path for diagnostics,
118-
* where the recorded compilation unit is saved.
119-
*/
118+
DebugOptions.DiagnoseOptions, diagnoseOptionValue);
119+
// Run a regular compilation with a forced crash.
120120
HotSpotCompilationRequestResult regularResult = runRegularCompilation(method, crashAndDiagnoseOptions);
121121
assertTrue(regularResult.getFailure() != null);
122+
// Replay and check that the compilation task ends with the same exception.
123+
replayCompilation(findReplayCompFile(temp.path), crashOptions, false);
124+
});
125+
}
122126

123-
// Replay the compilation without forcing a crash and enable diagnostic options.
127+
@Test
128+
public void recordsAndReplaysWithDiagnosticOptions() throws Throwable {
129+
reverseAndUppercase(List.of("foo", "bar", "baz"));
130+
runTest((temp) -> {
131+
ResolvedJavaMethod method = getResolvedJavaMethod("reverseAndUppercase");
132+
OptionValues initialOptions = getInitialOptions();
133+
OptionValues recordOptions = new OptionValues(initialOptions, DebugOptions.RecordForReplay, "*",
134+
DebugOptions.DumpPath, temp.toString());
135+
runRegularCompilation(method, recordOptions);
136+
// Replay with the same options and verify the graphs are equal.
137+
Path replayFile = findReplayCompFile(temp.path);
138+
replayCompilation(replayFile, initialOptions, true);
139+
/*
140+
* Replay with diagnostic options. Do not check graph equality, since enabling graph
141+
* dumps typically changes the graphs.
142+
*/
124143
EconomicSet<DebugOptions.OptimizationLogTarget> logTargets = EconomicSet.create();
125144
logTargets.add(DebugOptions.OptimizationLogTarget.Stdout);
126-
OptionValues replayOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(),
145+
OptionValues diagnosticOptions = new OptionValues(initialOptions, DebugOptions.DumpPath, temp.toString(),
127146
DebugOptions.PrintGraph, DebugOptions.PrintGraphTarget.File, DebugOptions.Dump, ":1",
128147
DebugOptions.OptimizationLog, logTargets, DebugOptions.Log, "", DebugOptions.PrintBackendCFG, true);
129-
replayCompilation(findReplayCompFile(temp.path), replayOptions);
148+
replayCompilation(replayFile, diagnosticOptions, false);
130149
});
131150
}
132151

@@ -204,14 +223,14 @@ private static HotSpotCompilationRequestResult runRegularCompilation(ResolvedJav
204223
return task.runCompilation(options);
205224
}
206225

207-
private static void replayCompilation(Path replayCompFile, OptionValues options) throws ReplayCompilationRunner.ReplayLauncherFailure {
226+
private static void replayCompilation(Path replayCompFile, OptionValues options, boolean compareGraphs) throws ReplayCompilationRunner.ReplayLauncherFailure {
208227
CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build();
209228
HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime();
210229
RuntimeProvider runtimeProvider = Graal.getRequiredCapability(RuntimeProvider.class);
211230
CompilerConfigurationFactory configFactory = CompilerConfigurationFactory.selectFactory(runtimeProvider.getCompilerConfigurationName(), options, jvmciRuntime);
212231
try (ReplayCompilationRunner.Reproducer reproducer = ReplayCompilationRunner.Reproducer.initializeFromFile(replayCompFile.toString(),
213232
declarations, jvmciRuntime, options, configFactory, new GlobalMetrics(), TTY.out().out(), EconomicMap.create())) {
214-
reproducer.compile().verify(false);
233+
reproducer.compile().compareCompilationProducts(compareGraphs);
215234
}
216235
}
217236

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/CompilationTask.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,14 @@ private HotSpotCompilationRequestResult performCompilationWithReplaySupport(Debu
408408
performRecompilationCheck(options, method);
409409
CompilationReplayBytecodes.add(debug, result.getBytecodeSize());
410410
} catch (Throwable e) {
411+
replaySupport.recordCompilationTaskException(e);
411412
throw debug.handle(e);
412413
}
413414
try (DebugCloseable b = CodeInstallationTime.start(debug)) {
414415
installMethod(selectedCompiler.getGraalRuntime().getHostBackend(), debug, graph, result);
415416
}
416417
printer.finish(result, installedCode);
417-
replaySupport.recordCompilationArtifacts(graph, result);
418+
replaySupport.recordCompilationTaskArtifacts(graph, result);
418419
return buildCompilationRequestResult(method);
419420
}
420421
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.graal.compiler.hotspot.replaycomp;
26+
27+
import jdk.graal.compiler.code.CompilationResult;
28+
import jdk.graal.compiler.nodes.StructuredGraph;
29+
import jdk.graal.compiler.printer.CanonicalStringGraphPrinter;
30+
31+
/**
32+
* The product of a recording or replay compilation task, which may be a successfully compiled graph
33+
* or an exception.
34+
*/
35+
public sealed interface CompilationTaskProduct {
36+
/**
37+
* Returns {@code true} if the product represents a successfully completed compilation task.
38+
*/
39+
boolean isSuccess();
40+
41+
/**
42+
* The product of a compilation task that failed with an exception.
43+
*
44+
* @param className the class name of the exception
45+
* @param stackTrace the stack trace of the exception
46+
*/
47+
record CompilationTaskException(String className, String stackTrace) implements CompilationTaskProduct {
48+
/**
49+
* Represents an unknown exception recorded by an earlier compiler version, which did not
50+
* record this information. This is temporarily needed for compatibility with older replay
51+
* files.
52+
*/
53+
public static final CompilationTaskException UNKNOWN = new CompilationTaskException("unknown", "unknown");
54+
55+
@Override
56+
public boolean isSuccess() {
57+
return false;
58+
}
59+
}
60+
61+
/**
62+
* The artifacts produced by a successful compilation task on this VM.
63+
*
64+
* @param graph the final graph
65+
* @param result the compilation result
66+
*/
67+
record CompilationTaskArtifacts(StructuredGraph graph, CompilationResult result) implements CompilationTaskProduct {
68+
/**
69+
* Returns the canonical graph string for the final graph.
70+
*/
71+
public String finalCanonicalGraph() {
72+
return CanonicalStringGraphPrinter.getCanonicalGraphString(graph, false, true);
73+
}
74+
75+
/**
76+
* Converts the object to a serializable representation that can be loaded during replay.
77+
*/
78+
public RecordedCompilationTaskArtifacts asRecordedArtifacts() {
79+
return new RecordedCompilationTaskArtifacts(finalCanonicalGraph());
80+
}
81+
82+
@Override
83+
public boolean isSuccess() {
84+
return true;
85+
}
86+
}
87+
88+
/**
89+
* The final graph produced by a successful compilation task. This is a serializable subset of
90+
* {@link CompilationTaskArtifacts} used for recorded compilation tasks.
91+
*
92+
* @param finalGraph the final canonical graph
93+
*/
94+
record RecordedCompilationTaskArtifacts(String finalGraph) implements CompilationTaskProduct {
95+
@Override
96+
public boolean isSuccess() {
97+
return true;
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)