Skip to content

Commit 70a2b44

Browse files
committed
[GR-54673] Add MaximumCompilations option.
PullRequest: graal/19608
2 parents ff759bc + 7510708 commit 70a2b44

File tree

18 files changed

+494
-23
lines changed

18 files changed

+494
-23
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
* Copyright (c) 2024, 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 com.oracle.truffle.runtime.OptimizedRuntimeOptions.SingleTierCompilationThreshold;
28+
29+
import java.util.concurrent.CountDownLatch;
30+
import java.util.concurrent.atomic.AtomicBoolean;
31+
import java.util.concurrent.atomic.AtomicReference;
32+
import java.util.function.Supplier;
33+
34+
import org.graalvm.polyglot.Context;
35+
import org.junit.Assert;
36+
import org.junit.Assume;
37+
import org.junit.Test;
38+
39+
import com.oracle.truffle.api.CallTarget;
40+
import com.oracle.truffle.api.CompilerDirectives;
41+
import com.oracle.truffle.api.Truffle;
42+
import com.oracle.truffle.api.dsl.Cached;
43+
import com.oracle.truffle.api.dsl.Specialization;
44+
import com.oracle.truffle.api.frame.VirtualFrame;
45+
import com.oracle.truffle.api.nodes.DirectCallNode;
46+
import com.oracle.truffle.api.nodes.ExecutableNode;
47+
import com.oracle.truffle.api.nodes.Node;
48+
import com.oracle.truffle.api.nodes.RootNode;
49+
import com.oracle.truffle.compiler.TruffleCompilerListener;
50+
import com.oracle.truffle.runtime.AbstractCompilationTask;
51+
import com.oracle.truffle.runtime.OptimizedCallTarget;
52+
import com.oracle.truffle.runtime.OptimizedTruffleRuntime;
53+
import com.oracle.truffle.runtime.OptimizedTruffleRuntimeListener;
54+
55+
/*
56+
* Test that reproduces the not-yet fixed bug GR-42688 and makes sure the issue is alleviated by MaximumCompilations limit.
57+
*/
58+
public class GR42688Test {
59+
60+
@SuppressWarnings("truffle-inlining")
61+
public abstract static class ClassNode extends Node {
62+
63+
public abstract Class<?> execute(Object value);
64+
65+
@SuppressWarnings("unused")
66+
@Specialization
67+
public Class<?> doInt(int n) {
68+
return Integer.class;
69+
}
70+
71+
@SuppressWarnings("unused")
72+
@Specialization
73+
public Class<?> doDouble(double n) {
74+
return Double.class;
75+
}
76+
}
77+
78+
public abstract static class CalleeNode extends ExecutableNode {
79+
protected CalleeNode() {
80+
super(null);
81+
}
82+
83+
@Override
84+
public final Object execute(VirtualFrame frame) {
85+
return execute(frame.getArguments()[0], frame.getArguments()[1]);
86+
}
87+
88+
public abstract Object execute(Object value, Object klass);
89+
90+
// One classNode per specialization instance, intended here and representative of more
91+
// complex nodes which also create new helper node instances for new specialization
92+
// instances
93+
@SuppressWarnings("unused")
94+
@Specialization(guards = "classNode.execute(value) == cachedClass", limit = "4")
95+
public boolean doCached(Object value, Class<?> checkedAgainst,
96+
@Cached ClassNode classNode,
97+
@Cached("classNode.execute(value)") Class<?> cachedClass) {
98+
return cachedClass == checkedAgainst;
99+
}
100+
}
101+
102+
public static class CalleeRoot extends RootNode {
103+
104+
@Child private CalleeNode calleeNode;
105+
private final CountDownLatch compilationStartLatch;
106+
private final CountDownLatch compilationFinishedLatch;
107+
108+
protected CalleeRoot(CountDownLatch compilationStartLatch, CountDownLatch compilationFinishedLatch) {
109+
super(null);
110+
this.compilationStartLatch = compilationStartLatch;
111+
this.compilationFinishedLatch = compilationFinishedLatch;
112+
this.calleeNode = GR42688TestFactory.CalleeNodeGen.create();
113+
}
114+
115+
@Override
116+
public final Object execute(VirtualFrame frame) {
117+
if (CompilerDirectives.inInterpreter()) {
118+
if (frame.getArguments()[0] instanceof Double) {
119+
compilationStartLatch.countDown();
120+
try {
121+
compilationFinishedLatch.await();
122+
} catch (InterruptedException ie) {
123+
throw new AssertionError(ie);
124+
}
125+
}
126+
}
127+
return calleeNode.execute(frame);
128+
}
129+
130+
@Override
131+
public String getName() {
132+
return "callee";
133+
}
134+
135+
@Override
136+
public String toString() {
137+
return getName();
138+
}
139+
}
140+
141+
private static class CallerRoot extends RootNode {
142+
@Child private DirectCallNode callNode;
143+
private final String name;
144+
145+
CallerRoot(CallTarget target, String name) {
146+
super(null);
147+
this.name = name;
148+
this.callNode = Truffle.getRuntime().createDirectCallNode(target);
149+
}
150+
151+
@Override
152+
public Object execute(VirtualFrame frame) {
153+
return callNode.call(frame.getArguments());
154+
}
155+
156+
@Override
157+
public String getName() {
158+
return name;
159+
}
160+
161+
@Override
162+
public String toString() {
163+
return getName();
164+
}
165+
}
166+
167+
@Test
168+
public void testDeoptLoopStoppedByMaximumCompilations() {
169+
Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime);
170+
OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime();
171+
AtomicReference<OptimizedCallTarget> calleeRef = new AtomicReference<>();
172+
AtomicReference<OptimizedCallTarget> callerRef = new AtomicReference<>();
173+
CountDownLatch calleeCompilationStartLatch = new CountDownLatch(1);
174+
CountDownLatch calleeCompilationFinishedLatch = new CountDownLatch(1);
175+
AtomicBoolean intCallerCompilationFailed = new AtomicBoolean();
176+
optimizedTruffleRuntime.addListener(new OptimizedTruffleRuntimeListener() {
177+
@Override
178+
public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilationTask task, TruffleCompilerListener.GraphInfo graph, TruffleCompilerListener.CompilationResultInfo result) {
179+
if (target == calleeRef.get()) {
180+
calleeCompilationFinishedLatch.countDown();
181+
}
182+
}
183+
184+
@Override
185+
public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilationTask task) {
186+
if (target == calleeRef.get()) {
187+
try {
188+
calleeCompilationStartLatch.await();
189+
} catch (InterruptedException ie) {
190+
throw new AssertionError(ie);
191+
}
192+
}
193+
}
194+
195+
@Override
196+
public void onCompilationFailed(OptimizedCallTarget target, String reason, boolean bailout, boolean permanentBailout, int tier, Supplier<String> lazyStackTrace) {
197+
if (target == callerRef.get() && "Maximum compilation count 100 reached.".equals(reason)) {
198+
intCallerCompilationFailed.set(true);
199+
}
200+
}
201+
});
202+
try (Context context = Context.newBuilder().allowExperimentalOptions(true).option("engine.CompileImmediately", "false").option("engine.BackgroundCompilation", "true").option(
203+
"engine.DynamicCompilationThresholds", "false").option("engine.MultiTier", "false").option("engine.Splitting", "false").option("engine.SingleTierCompilationThreshold",
204+
"10").option("engine.CompilationFailureAction", "Silent").build()) {
205+
context.enter();
206+
OptimizedCallTarget callee = (OptimizedCallTarget) new CalleeRoot(calleeCompilationStartLatch, calleeCompilationFinishedLatch).getCallTarget();
207+
OptimizedCallTarget intCaller = (OptimizedCallTarget) new CallerRoot(callee, "intCaller").getCallTarget();
208+
calleeRef.set(callee);
209+
callerRef.set(intCaller);
210+
211+
final int compilationThreshold = callee.getOptionValue(SingleTierCompilationThreshold);
212+
213+
for (int i = 0; i < compilationThreshold; i++) {
214+
callee.call(42, Integer.class);
215+
}
216+
/*
217+
* The loop above triggers compilation in a separate thread, but using a compilation
218+
* listener, the compilation is delayed until the following call reaches a certain
219+
* point.
220+
*/
221+
callee.call(3.14, Double.class);
222+
Assert.assertTrue(callee.isValid());
223+
/*
224+
* The compiled callee is still valid for integer, because the compilation finished
225+
* before the AST respecialized for double. The respecialization for double made it
226+
* invalid for integer, it would need another specialization round to be valid both for
227+
* integer and double.
228+
*/
229+
callee.call(42, Integer.class);
230+
Assert.assertTrue(callee.isValid());
231+
/*
232+
* The intCaller uses the compiled callee for executions in the interpreter, which is
233+
* valid for integer, but when intCaller is compiled, in inlines the AST for callee,
234+
* which is not valid for integer and calling the compiled callee results in an
235+
* immediate deopt, but the deopt lands outside the callee AST, which means the compiled
236+
* callee is used again without the AST getting a chance to respecialize. Therefore, the
237+
* deopts of the intCaller are repeated until the MaximumRepeatedCompilations limit
238+
* kicks in and marks the intCaller call target as permanent opt fail which means no
239+
* further compilations of it are attempted.
240+
*/
241+
for (int i = 0; i < 1000000000 && !intCallerCompilationFailed.get(); i++) {
242+
intCaller.call(42, Integer.class);
243+
}
244+
Assert.assertTrue(intCallerCompilationFailed.get());
245+
}
246+
}
247+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2024, 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 java.util.concurrent.atomic.AtomicBoolean;
28+
import java.util.concurrent.atomic.AtomicReference;
29+
import java.util.function.Supplier;
30+
31+
import org.graalvm.polyglot.Context;
32+
import org.junit.Assume;
33+
import org.junit.Test;
34+
35+
import com.oracle.truffle.api.CallTarget;
36+
import com.oracle.truffle.api.CompilerDirectives;
37+
import com.oracle.truffle.api.Truffle;
38+
import com.oracle.truffle.api.frame.VirtualFrame;
39+
import com.oracle.truffle.api.nodes.RootNode;
40+
import com.oracle.truffle.runtime.OptimizedCallTarget;
41+
import com.oracle.truffle.runtime.OptimizedTruffleRuntime;
42+
import com.oracle.truffle.runtime.OptimizedTruffleRuntimeListener;
43+
44+
public class MaximumCompilationsTest {
45+
public static class AllwaysDeoptRoot extends RootNode {
46+
47+
protected AllwaysDeoptRoot() {
48+
super(null);
49+
}
50+
51+
@Override
52+
public final Object execute(VirtualFrame frame) {
53+
CompilerDirectives.transferToInterpreterAndInvalidate();
54+
return null;
55+
}
56+
57+
@Override
58+
public String getName() {
59+
return "allwaysDeopt";
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return getName();
65+
}
66+
}
67+
68+
@Test
69+
public void testMaximumCompilations() {
70+
Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime);
71+
OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime();
72+
AtomicReference<CallTarget> callTargetRef = new AtomicReference<>();
73+
AtomicBoolean callTargetCompilationFailed = new AtomicBoolean();
74+
optimizedTruffleRuntime.addListener(new OptimizedTruffleRuntimeListener() {
75+
@Override
76+
public void onCompilationFailed(OptimizedCallTarget target, String reason, boolean bailout, boolean permanentBailout, int tier, Supplier<String> lazyStackTrace) {
77+
if (target == callTargetRef.get() && "Maximum compilation count 100 reached.".equals(reason)) {
78+
callTargetCompilationFailed.set(true);
79+
}
80+
}
81+
});
82+
83+
try (Context context = Context.newBuilder().option("engine.CompilationFailureAction", "Silent").build()) {
84+
context.enter();
85+
CallTarget callTarget = new AllwaysDeoptRoot().getCallTarget();
86+
callTargetRef.set(callTarget);
87+
while (!callTargetCompilationFailed.get()) {
88+
callTarget.call();
89+
}
90+
}
91+
}
92+
}

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

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

27+
import static jdk.graal.compiler.hotspot.libgraal.truffle.BuildTime.getHostMethodHandleOrFail;
28+
29+
import java.lang.invoke.MethodHandle;
30+
import java.util.Map;
31+
import java.util.function.Supplier;
32+
2733
import com.oracle.truffle.compiler.TruffleCompilable;
2834
import com.oracle.truffle.compiler.hotspot.libgraal.TruffleFromLibGraal.Id;
35+
2936
import jdk.graal.compiler.debug.GraalError;
3037
import jdk.graal.compiler.hotspot.HotSpotGraalServices;
3138
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
3239
import jdk.vm.ci.meta.JavaConstant;
3340
import jdk.vm.ci.meta.SpeculationLog;
3441

35-
import java.lang.invoke.MethodHandle;
36-
import java.util.Map;
37-
import java.util.function.Supplier;
38-
39-
import static jdk.graal.compiler.hotspot.libgraal.truffle.BuildTime.getHostMethodHandleOrFail;
40-
4142
final class HSTruffleCompilable extends HSIndirectHandle implements TruffleCompilable {
4243

4344
private static final Handles HANDLES = new Handles();
@@ -125,6 +126,11 @@ public void onCompilationFailed(Supplier<String> serializedException, boolean su
125126
}
126127
}
127128

129+
@Override
130+
public void onCompilationSuccess(int compilationTier, boolean lastTier) {
131+
NativeImageHostCalls.onCompilationSuccess(hsHandle, compilationTier, lastTier);
132+
}
133+
128134
@Override
129135
public boolean onInvalidate(Object source, CharSequence reason, boolean wasActive) {
130136
throw GraalError.shouldNotReachHere("Should not be reachable."); // ExcludeFromJacocoGeneratedReport

0 commit comments

Comments
 (0)