Skip to content

Commit fe5a733

Browse files
tkrodriguezjchalou
authored andcommitted
[GR-43908] Automatically detect deopt recompile cycles
PullRequest: graal/13708
2 parents 244745f + 67a957e commit fe5a733

File tree

50 files changed

+1670
-137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1670
-137
lines changed

compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/truffle/HSTruffleCompilable.java

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,6 @@
2424
*/
2525
package jdk.graal.compiler.libgraal.truffle;
2626

27-
import com.oracle.truffle.compiler.TruffleCompilable;
28-
import com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal;
29-
import jdk.graal.compiler.debug.GraalError;
30-
import jdk.graal.compiler.hotspot.HotSpotGraalServices;
31-
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
32-
import jdk.vm.ci.meta.JavaConstant;
33-
import jdk.vm.ci.meta.SpeculationLog;
34-
import org.graalvm.jniutils.HSObject;
35-
import org.graalvm.jniutils.JNI;
36-
import org.graalvm.jniutils.JNI.JByteArray;
37-
import org.graalvm.jniutils.JNI.JNIEnv;
38-
import org.graalvm.jniutils.JNI.JObject;
39-
import org.graalvm.jniutils.JNICalls;
40-
import org.graalvm.jniutils.JNICalls.JNIMethod;
41-
import org.graalvm.jniutils.JNIMethodScope;
42-
import org.graalvm.jniutils.JNIUtil;
43-
import org.graalvm.nativeimage.StackValue;
44-
import org.graalvm.word.WordFactory;
45-
46-
import java.util.LinkedHashMap;
47-
import java.util.Map;
48-
import java.util.function.Supplier;
49-
5027
import static com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal.Id.AsJavaConstant;
5128
import static com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal.Id.CancelCompilation;
5229
import static com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal.Id.CompilableToString;
@@ -66,9 +43,35 @@
6643
import static org.graalvm.jniutils.JNIMethodScope.env;
6744
import static org.graalvm.jniutils.JNIUtil.createString;
6845

46+
import java.util.LinkedHashMap;
47+
import java.util.Map;
48+
import java.util.function.Supplier;
49+
50+
import org.graalvm.jniutils.HSObject;
51+
import org.graalvm.jniutils.JNI;
52+
import org.graalvm.jniutils.JNI.JByteArray;
53+
import org.graalvm.jniutils.JNI.JNIEnv;
54+
import org.graalvm.jniutils.JNI.JObject;
55+
import org.graalvm.jniutils.JNICalls;
56+
import org.graalvm.jniutils.JNICalls.JNIMethod;
57+
import org.graalvm.jniutils.JNIMethodScope;
58+
import org.graalvm.jniutils.JNIUtil;
59+
import org.graalvm.nativeimage.StackValue;
60+
import org.graalvm.word.WordFactory;
61+
62+
import com.oracle.truffle.compiler.TruffleCompilable;
63+
import com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal;
64+
65+
import jdk.graal.compiler.debug.GraalError;
66+
import jdk.graal.compiler.hotspot.HotSpotGraalServices;
67+
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
68+
import jdk.vm.ci.meta.JavaConstant;
69+
import jdk.vm.ci.meta.SpeculationLog;
70+
6971
final class HSTruffleCompilable extends HSObject implements TruffleCompilable {
7072

7173
private static volatile JNIMethod prepareForCompilationNewMethod;
74+
private static volatile JNIMethod getSuccessfulCompilationCountMethod;
7275
private static volatile JNIMethod onCompilationSuccessMethod;
7376

7477
private final TruffleFromLibGraalCalls calls;
@@ -147,6 +150,31 @@ private boolean callPrepareForCompilationNew(JNIMethod method, JNIEnv env, JObje
147150
return calls.getJNICalls().callStaticBoolean(env, calls.getPeer(), method, args);
148151
}
149152

153+
@Override
154+
public int getSuccessfulCompilationCount() {
155+
JNIEnv env = JNIMethodScope.env();
156+
JNIMethod method = findGetSuccessfulCompilationCountMethod(env);
157+
if (method != null) {
158+
return callGetSuccessfulCompilationCountMethod(method, env, getHandle());
159+
}
160+
return 0;
161+
}
162+
163+
private JNIMethod findGetSuccessfulCompilationCountMethod(JNIEnv env) {
164+
JNIMethod res = getSuccessfulCompilationCountMethod;
165+
if (res == null) {
166+
res = calls.findJNIMethod(env, "getSuccessfulCompilationCount", int.class, Object.class);
167+
getSuccessfulCompilationCountMethod = res;
168+
}
169+
return res.getJMethodID().isNonNull() ? res : null;
170+
}
171+
172+
private int callGetSuccessfulCompilationCountMethod(JNIMethod method, JNIEnv env, JObject p0) {
173+
JNI.JValue args = StackValue.get(1, JNI.JValue.class);
174+
args.addressOf(0).setJObject(p0);
175+
return calls.getJNICalls().callStaticInt(env, calls.getPeer(), method, args);
176+
}
177+
150178
@TruffleFromLibGraal(IsTrivial)
151179
@Override
152180
public boolean isTrivial() {

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyProfileMethodUsage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@
2727
import java.lang.reflect.Method;
2828

2929
import org.graalvm.collections.EconomicSet;
30+
3031
import jdk.graal.compiler.debug.GraalError;
32+
import jdk.graal.compiler.hotspot.HotSpotGraalServices;
3133
import jdk.graal.compiler.nodes.StructuredGraph;
3234
import jdk.graal.compiler.nodes.java.MethodCallTargetNode;
3335
import jdk.graal.compiler.nodes.spi.CoreProviders;
3436
import jdk.graal.compiler.nodes.spi.ProfileProvider;
3537
import jdk.graal.compiler.nodes.spi.ResolvedJavaMethodProfileProvider;
3638
import jdk.graal.compiler.nodes.spi.StableProfileProvider;
3739
import jdk.graal.compiler.phases.VerifyPhase;
38-
3940
import jdk.vm.ci.meta.ResolvedJavaMethod;
4041
import jdk.vm.ci.meta.ResolvedJavaType;
4142

@@ -59,6 +60,7 @@ public class VerifyProfileMethodUsage extends VerifyPhase<CoreProviders> {
5960
ALLOWED_CLASSES.add(StableProfileProvider.CachingProfilingInfo.class);
6061
ALLOWED_CLASSES.add(ResolvedJavaMethodProfileProvider.class);
6162
ALLOWED_CLASSES.add(ResolvedJavaMethod.class);
63+
ALLOWED_CLASSES.add(HotSpotGraalServices.class);
6264
}
6365

6466
@Override
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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.truffle.test;
26+
27+
import static org.junit.Assert.assertEquals;
28+
import static org.junit.Assert.assertNotNull;
29+
import static org.junit.Assert.assertTrue;
30+
31+
import java.util.concurrent.atomic.AtomicReference;
32+
import java.util.function.Consumer;
33+
import java.util.function.Supplier;
34+
35+
import org.graalvm.polyglot.Context;
36+
import org.junit.After;
37+
import org.junit.Assume;
38+
import org.junit.Before;
39+
import org.junit.Test;
40+
41+
import com.oracle.truffle.api.Assumption;
42+
import com.oracle.truffle.api.CallTarget;
43+
import com.oracle.truffle.api.CompilerDirectives;
44+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
45+
import com.oracle.truffle.api.Truffle;
46+
import com.oracle.truffle.api.frame.VirtualFrame;
47+
import com.oracle.truffle.api.nodes.RootNode;
48+
import com.oracle.truffle.compiler.TruffleCompilerListener;
49+
import com.oracle.truffle.runtime.AbstractCompilationTask;
50+
import com.oracle.truffle.runtime.OptimizedCallTarget;
51+
import com.oracle.truffle.runtime.OptimizedTruffleRuntime;
52+
import com.oracle.truffle.runtime.OptimizedTruffleRuntimeListener;
53+
54+
import jdk.graal.compiler.truffle.TruffleCompilerOptions;
55+
56+
public class DeoptLoopDetectionTest {
57+
58+
private final AtomicReference<CallTarget> callTargetFilter = new AtomicReference<>();
59+
private final AtomicReference<Boolean> compilationResult = new AtomicReference<>();
60+
private final AtomicReference<String> compilationFailedReason = new AtomicReference<>();
61+
private Context context;
62+
private final OptimizedTruffleRuntimeListener listener = new OptimizedTruffleRuntimeListener() {
63+
@Override
64+
public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilationTask task, TruffleCompilerListener.GraphInfo graph, TruffleCompilerListener.CompilationResultInfo result) {
65+
OptimizedTruffleRuntimeListener.super.onCompilationSuccess(target, task, graph, result);
66+
if (target == callTargetFilter.get()) {
67+
compilationResult.set(Boolean.TRUE);
68+
}
69+
}
70+
71+
@Override
72+
public void onCompilationFailed(OptimizedCallTarget target, String reason, boolean bailout, boolean permanentBailout, int tier, Supplier<String> lazyStackTrace) {
73+
if (target == callTargetFilter.get()) {
74+
compilationResult.set(Boolean.FALSE);
75+
compilationFailedReason.set(reason);
76+
}
77+
}
78+
};
79+
80+
@Before
81+
public void setup() {
82+
context = Context.newBuilder().//
83+
option("engine.CompilationFailureAction", "Silent").//
84+
option("engine.BackgroundCompilation", "false").//
85+
option("engine.CompileImmediately", "true").build();
86+
context.enter();
87+
Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime);
88+
((OptimizedTruffleRuntime) Truffle.getRuntime()).addListener(listener);
89+
90+
}
91+
92+
@After
93+
public void tearDown() {
94+
context.close();
95+
((OptimizedTruffleRuntime) Truffle.getRuntime()).removeListener(listener);
96+
}
97+
98+
@Test
99+
public void testAlwaysDeopt() {
100+
assertDeoptLoop(new BaseRootNode() {
101+
@Override
102+
public Object execute(VirtualFrame frame) {
103+
CompilerDirectives.transferToInterpreterAndInvalidate();
104+
return null;
105+
}
106+
}, "alwaysDeopt", CallTarget::call, 1);
107+
}
108+
109+
@Test
110+
public void testLocalDeopt() {
111+
assertDeoptLoop(new BaseRootNode() {
112+
113+
@CompilationFinal boolean cachedValue;
114+
115+
@Override
116+
public Object execute(VirtualFrame frame) {
117+
boolean arg = (boolean) frame.getArguments()[0];
118+
if (this.cachedValue != arg) {
119+
CompilerDirectives.transferToInterpreterAndInvalidate();
120+
this.cachedValue = arg;
121+
}
122+
return this.cachedValue;
123+
}
124+
125+
}, "localDeopt", (target) -> {
126+
target.call(true);
127+
target.call(false);
128+
}, 2);
129+
}
130+
131+
@Test
132+
public void testGlobalDeopt() {
133+
assertDeoptLoop(new BaseRootNode() {
134+
135+
@CompilationFinal Assumption assumption = Assumption.create();
136+
137+
@Override
138+
public Object execute(VirtualFrame frame) {
139+
boolean arg = (boolean) frame.getArguments()[0];
140+
if (arg && assumption.isValid()) {
141+
CompilerDirectives.transferToInterpreterAndInvalidate();
142+
assumption.invalidate();
143+
assumption = Assumption.create();
144+
}
145+
return arg;
146+
}
147+
148+
}, "globalDeopt", (target) -> {
149+
target.call(true);
150+
}, 1);
151+
}
152+
153+
@Test
154+
public void testLocalDeoptWithChangedCode() {
155+
assertDeoptLoop(new BaseRootNode() {
156+
157+
@CompilationFinal boolean cachedValue;
158+
159+
@Override
160+
public Object execute(VirtualFrame frame) {
161+
int arg = (int) frame.getArguments()[0];
162+
if (arg > 0) {
163+
CompilerDirectives.transferToInterpreterAndInvalidate();
164+
this.cachedValue = !this.cachedValue;
165+
}
166+
int result = arg;
167+
if (cachedValue) {
168+
result--;
169+
} else {
170+
result++;
171+
}
172+
return result;
173+
}
174+
175+
}, "localLoopDeoptwithChangedCode", (target) -> {
176+
target.call(1);
177+
}, 1);
178+
}
179+
180+
@Test
181+
public void testStabilizeLate() {
182+
assertDeoptLoop(new BaseRootNode() {
183+
184+
static final int GENERIC = Integer.MIN_VALUE;
185+
186+
@CompilationFinal int cachedValue;
187+
int seenCount = 0;
188+
189+
@Override
190+
public Object execute(VirtualFrame frame) {
191+
int arg = (int) frame.getArguments()[0];
192+
// we assume Integer.MIN_VALUE is never used in value
193+
assert arg != Integer.MIN_VALUE;
194+
195+
int result;
196+
if (cachedValue == GENERIC) {
197+
result = arg;
198+
} else {
199+
if (cachedValue != arg) {
200+
CompilerDirectives.transferToInterpreterAndInvalidate();
201+
if (seenCount < 20) {
202+
this.cachedValue = arg;
203+
} else {
204+
cachedValue = GENERIC;
205+
}
206+
seenCount++;
207+
result = arg;
208+
} else {
209+
result = cachedValue;
210+
}
211+
}
212+
return result;
213+
}
214+
215+
}, "stabilizeLate", new Consumer<>() {
216+
217+
int input = 1;
218+
219+
@Override
220+
public void accept(CallTarget target) {
221+
target.call(input++);
222+
}
223+
}, 1);
224+
}
225+
226+
private static final int MAX_EXECUTIONS = 1024;
227+
228+
private void assertDeoptLoop(BaseRootNode root, String name, Consumer<CallTarget> callStrategy, int compilationsPerIteration) {
229+
root.name = name;
230+
CallTarget callTarget = root.getCallTarget();
231+
callTargetFilter.set(callTarget);
232+
compilationResult.set(null);
233+
compilationFailedReason.set(null);
234+
235+
callStrategy.accept(callTarget);
236+
237+
assertEquals(Boolean.TRUE, compilationResult.get());
238+
int iterationCounter = 0;
239+
while (compilationResult.get()) {
240+
if (iterationCounter >= MAX_EXECUTIONS) {
241+
throw new AssertionError("No deopt loop detected after " + MAX_EXECUTIONS + " executions");
242+
}
243+
callStrategy.accept(callTarget);
244+
iterationCounter++;
245+
}
246+
247+
assertTrue(iterationCounter * compilationsPerIteration > TruffleCompilerOptions.DeoptCycleDetectionThreshold.getDefaultValue());
248+
assertEquals(Boolean.FALSE, compilationResult.get());
249+
String failedReason = compilationFailedReason.get();
250+
assertNotNull(failedReason);
251+
assertTrue(failedReason, failedReason.contains("Deopt taken too many times"));
252+
}
253+
254+
abstract static class BaseRootNode extends RootNode {
255+
256+
private String name = this.getClass().getSimpleName();
257+
258+
BaseRootNode() {
259+
super(null);
260+
}
261+
262+
@Override
263+
public String getName() {
264+
return name;
265+
}
266+
267+
@Override
268+
public String toString() {
269+
return getName();
270+
}
271+
}
272+
}

0 commit comments

Comments
 (0)