Skip to content

Commit 4866eaa

Browse files
committed
8273409: Receiver type narrowed by CCP does not always trigger post-parse call devirtualization
Reviewed-by: vlivanov, neliasso
1 parent 5ca26cb commit 4866eaa

File tree

3 files changed

+205
-1
lines changed

3 files changed

+205
-1
lines changed

src/hotspot/share/opto/phaseX.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1967,7 +1967,9 @@ Node *PhaseCCP::transform_once( Node *n ) {
19671967

19681968
// TEMPORARY fix to ensure that 2nd GVN pass eliminates NULL checks
19691969
switch( n->Opcode() ) {
1970-
case Op_FastLock: // Revisit FastLocks for lock coarsening
1970+
case Op_CallStaticJava: // Give post-parse call devirtualization a chance
1971+
case Op_CallDynamicJava:
1972+
case Op_FastLock: // Revisit FastLocks for lock coarsening
19711973
case Op_If:
19721974
case Op_CountedLoopEnd:
19731975
case Op_Region:
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright (c) 2021, 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package compiler.c2.irTests;
25+
26+
import java.lang.invoke.MethodHandle;
27+
import java.lang.invoke.MethodHandles;
28+
import java.lang.invoke.MethodType;
29+
import jdk.test.lib.Asserts;
30+
import compiler.lib.ir_framework.*;
31+
32+
/*
33+
* @test
34+
* @bug 8273409
35+
* @summary Test that post-parse call devirtualization works as intended.
36+
* @library /test/lib /
37+
* @run driver compiler.c2.irTests.TestPostParseCallDevirtualization
38+
*/
39+
public class TestPostParseCallDevirtualization {
40+
41+
public static void main(String[] args) {
42+
TestFramework framework = new TestFramework();
43+
Scenario noOSR = new Scenario(0, "-XX:-UseOnStackReplacement");
44+
Scenario alwaysIncremental = new Scenario(1, "-XX:-UseOnStackReplacement", "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+AlwaysIncrementalInline");
45+
framework.addScenarios(noOSR, alwaysIncremental).start();
46+
}
47+
48+
static interface I {
49+
public int method();
50+
}
51+
52+
static final class A implements I {
53+
@Override
54+
public int method() { return 0; };
55+
}
56+
57+
static final class B implements I {
58+
@Override
59+
public int method() { return 42; };
60+
}
61+
62+
static final class C implements I {
63+
@Override
64+
public int method() { return -1; };
65+
}
66+
67+
static final A a = new A();
68+
static final B b = new B();
69+
static final C c = new C();
70+
71+
static int callHelper(I recv) {
72+
// Receiver profile is polluted
73+
return recv.method();
74+
}
75+
76+
@Test
77+
@IR(failOn = {IRNode.DYNAMIC_CALL_OF_METHOD, "method"},
78+
counts = {IRNode.STATIC_CALL_OF_METHOD, "method", "= 1"})
79+
public int testDynamicCallWithLoop(B val) {
80+
// Make sure val is non-null below
81+
if (val == null) {
82+
return 0;
83+
}
84+
// Loop that triggers loop opts
85+
I recv = a;
86+
for (int i = 0; i < 3; ++i) {
87+
if (i > 1) {
88+
recv = val;
89+
}
90+
}
91+
// We only know after loop opts that the receiver type is non-null B.
92+
// Post-parse call devirtualization should then convert the
93+
// virtual call in the helper method to a static call.
94+
return callHelper(recv);
95+
}
96+
97+
@Run(test = "testDynamicCallWithLoop")
98+
public void checkTestDynamicCallWithLoop() {
99+
// Pollute receiver profile with three different
100+
// types to prevent (bimorphic) inlining.
101+
callHelper(a);
102+
callHelper(b);
103+
callHelper(c);
104+
Asserts.assertEquals(testDynamicCallWithLoop(b), 42);
105+
}
106+
107+
@Test
108+
@IR(failOn = {IRNode.DYNAMIC_CALL_OF_METHOD, "method"},
109+
counts = {IRNode.STATIC_CALL_OF_METHOD, "method", "= 1"})
110+
public int testDynamicCallWithCCP(B val) {
111+
// Make sure val is non-null below
112+
if (val == null) {
113+
return 0;
114+
}
115+
// Loop that triggers CCP
116+
I recv = a;
117+
for (int i = 0; i < 100; i++) {
118+
if ((i % 2) == 0) {
119+
recv = val;
120+
}
121+
}
122+
// We only know after CCP that the receiver type is non-null B.
123+
// Post-parse call devirtualization should then convert the
124+
// virtual call in the helper method to a static call.
125+
return callHelper(recv);
126+
}
127+
128+
@Run(test = "testDynamicCallWithCCP")
129+
public void checkTestDynamicCallWithCCP() {
130+
// Pollute receiver profile with three different
131+
// types to prevent (bimorphic) inlining.
132+
callHelper(a);
133+
callHelper(b);
134+
callHelper(c);
135+
Asserts.assertEquals(testDynamicCallWithCCP(b), 42);
136+
}
137+
138+
static final MethodHandle mh1;
139+
static final MethodHandle mh2;
140+
141+
static {
142+
try {
143+
MethodHandles.Lookup lookup = MethodHandles.lookup();
144+
mh1 = lookup.findStatic(TestPostParseCallDevirtualization.class, "method1", MethodType.methodType(int.class));
145+
mh2 = lookup.findStatic(TestPostParseCallDevirtualization.class, "method2", MethodType.methodType(int.class));
146+
} catch (NoSuchMethodException | IllegalAccessException e) {
147+
e.printStackTrace();
148+
throw new RuntimeException("Method handle lookup failed");
149+
}
150+
}
151+
152+
static int method1() { return 0; }
153+
static int method2() { return 42; }
154+
155+
@Test
156+
@IR(failOn = {IRNode.STATIC_CALL_OF_METHOD, "invokeBasic"},
157+
counts = {IRNode.STATIC_CALL_OF_METHOD, "invokeStatic", "= 1"})
158+
public int testMethodHandleCallWithLoop() throws Throwable {
159+
MethodHandle mh = mh1;
160+
for (int i = 0; i < 3; ++i) {
161+
if (i > 1) {
162+
mh = mh2;
163+
}
164+
}
165+
// We only know after loop opts that the receiver is mh2.
166+
// Post-parse call devirtualization should then convert the
167+
// virtual call to a static call.
168+
return (int)mh.invokeExact();
169+
}
170+
171+
@Run(test = "testMethodHandleCallWithLoop")
172+
public void checkTestMethodHandleCallWithLoop() throws Throwable {
173+
Asserts.assertEquals(testMethodHandleCallWithLoop(), 42);
174+
}
175+
176+
@Test
177+
@IR(failOn = {IRNode.STATIC_CALL_OF_METHOD, "invokeBasic"},
178+
counts = {IRNode.STATIC_CALL_OF_METHOD, "invokeStatic", "= 1"})
179+
public int testMethodHandleCallWithCCP() throws Throwable {
180+
MethodHandle mh = mh1;
181+
int limit = 0;
182+
for (int i = 0; i < 100; i++) {
183+
if ((i % 2) == 0) {
184+
limit = 1;
185+
}
186+
}
187+
for (int i = 0; i < limit; ++i) {
188+
mh = mh2;
189+
}
190+
// We only know after CCP that the receiver is mh2.
191+
// Post-parse call devirtualization should then convert the
192+
// virtual call to a static call.
193+
return (int)mh.invokeExact();
194+
}
195+
196+
@Run(test = "testMethodHandleCallWithCCP")
197+
public void checkTestMethodHandleCallWithCCP() throws Throwable {
198+
Asserts.assertEquals(testMethodHandleCallWithCCP(), 42);
199+
}
200+
}

test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ public class IRNode {
115115
public static final String COUNTEDLOOP_MAIN = START + "CountedLoop\\b" + MID + "main" + END;
116116

117117
public static final String CALL = START + "CallStaticJava" + MID + END;
118+
public static final String DYNAMIC_CALL_OF_METHOD = COMPOSITE_PREFIX + START + "CallDynamicJava" + MID + IS_REPLACED + END;
119+
public static final String STATIC_CALL_OF_METHOD = COMPOSITE_PREFIX + START + "CallStaticJava" + MID + IS_REPLACED + END;
118120
public static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap.*reason" + END;
119121
public static final String PREDICATE_TRAP = START + "CallStaticJava" + MID + "uncommon_trap.*predicate" + END;
120122
public static final String UNSTABLE_IF_TRAP = START + "CallStaticJava" + MID + "uncommon_trap.*unstable_if" + END;

0 commit comments

Comments
 (0)