1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 class Main1 implements Base {
18 }
19 
20 class Main2 extends Main1 {
foobar()21   public void foobar() {}
22 }
23 
24 class Main3 implements Base {
foo(int i)25   public int foo(int i) {
26     if (i != 3) {
27       printError("error3");
28     }
29     return -(i + 10);
30   }
31 }
32 
33 public class Main {
34   static Base sMain1;
35   static Base sMain2;
36   static Base sMain3;
37 
38   static boolean sIsOptimizing = true;
39   static boolean sHasJIT = true;
40   static volatile boolean sOtherThreadStarted;
41 
assertSingleImplementation(Class<?> clazz, String method_name, boolean b)42   private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
43     if (hasSingleImplementation(clazz, method_name) != b) {
44       System.out.println(clazz + "." + method_name +
45           " doesn't have single implementation value of " + b);
46     }
47   }
48 
getValue(Class<?> cls)49   static int getValue(Class<?> cls) {
50     if (cls == Main1.class || cls == Main2.class) {
51       return 1;
52     }
53     return 3;
54   }
55 
56   // sMain1.foo()/sMain2.foo() will be always be Base.foo() before Main3 is loaded/linked.
57   // So sMain1.foo() can be devirtualized to Base.foo() and be inlined.
58   // After Helper.createMain3() which links in Main3, live testImplement() on stack
59   // should be deoptimized.
testImplement(boolean createMain3, boolean wait, boolean setHasJIT)60   static void testImplement(boolean createMain3, boolean wait, boolean setHasJIT) {
61     if (setHasJIT) {
62       if (isInterpreted()) {
63         sHasJIT = false;
64       }
65       return;
66     }
67 
68     if (createMain3 && (sIsOptimizing || sHasJIT)) {
69       assertIsManaged();
70     }
71 
72     if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
73       System.out.println("11 expected.");
74     }
75     if (sMain1.$noinline$bar() != -1) {
76       System.out.println("-1 expected.");
77     }
78     if (sMain2.foo(getValue(sMain2.getClass())) != 11) {
79       System.out.println("11 expected.");
80     }
81 
82     if (createMain3) {
83       // Wait for the other thread to start.
84       while (!sOtherThreadStarted);
85       // Create an Main2 instance and assign it to sMain2.
86       // sMain1 is kept the same.
87       sMain3 = Helper.createMain3();
88       // Wake up the other thread.
89       synchronized(Main.class) {
90         Main.class.notify();
91       }
92     } else if (wait) {
93       // This is the other thread.
94       synchronized(Main.class) {
95         sOtherThreadStarted = true;
96         // Wait for Main2 to be linked and deoptimization is triggered.
97         try {
98           Main.class.wait();
99         } catch (Exception e) {
100         }
101       }
102     }
103 
104     // There should be a deoptimization here right after Main3 is linked by
105     // calling Helper.createMain3(), even though sMain1 didn't change.
106     // The behavior here would be different if inline-cache is used, which
107     // doesn't deoptimize since sMain1 still hits the type cache.
108     if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
109       System.out.println("11 expected.");
110     }
111     if ((createMain3 || wait) && sHasJIT && !sIsOptimizing) {
112       // This method should be deoptimized right after Main3 is created.
113       assertIsInterpreted();
114     }
115 
116     if (sMain3 != null) {
117       if (sMain3.foo(getValue(sMain3.getClass())) != -13) {
118         System.out.println("-13 expected.");
119       }
120     }
121   }
122 
123   // Test scenarios under which CHA-based devirtualization happens,
124   // and class loading that implements a method can invalidate compiled code.
main(String[] args)125   public static void main(String[] args) {
126     System.loadLibrary(args[0]);
127 
128     if (isInterpreted()) {
129       sIsOptimizing = false;
130     }
131 
132     // sMain1 is an instance of Main1.
133     // sMain2 is an instance of Main2.
134     // Neither Main1 nor Main2 override default method Base.foo().
135     // Main3 hasn't bee loaded yet.
136     sMain1 = new Main1();
137     sMain2 = new Main2();
138 
139     ensureJitCompiled(Main.class, "testImplement");
140     testImplement(false, false, true);
141 
142     if (sHasJIT && !sIsOptimizing) {
143       assertSingleImplementation(Base.class, "foo", true);
144       assertSingleImplementation(Main1.class, "foo", true);
145     } else {
146       // Main3 is verified ahead-of-time so it's linked in already.
147     }
148 
149     // Create another thread that also calls sMain1.foo().
150     // Try to test suspend and deopt another thread.
151     new Thread() {
152       public void run() {
153         testImplement(false, true, false);
154       }
155     }.start();
156 
157     // This will create Main3 instance in the middle of testImplement().
158     testImplement(true, false, false);
159     assertSingleImplementation(Base.class, "foo", false);
160     assertSingleImplementation(Main1.class, "foo", true);
161     assertSingleImplementation(sMain3.getClass(), "foo", true);
162   }
163 
ensureJitCompiled(Class<?> itf, String method_name)164   private static native void ensureJitCompiled(Class<?> itf, String method_name);
assertIsInterpreted()165   private static native void assertIsInterpreted();
assertIsManaged()166   private static native void assertIsManaged();
isInterpreted()167   private static native boolean isInterpreted();
hasSingleImplementation(Class<?> clazz, String method_name)168   private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
169 }
170 
171 // Put createMain3() in another class to avoid class loading due to verifier.
172 class Helper {
createMain3()173   static Base createMain3() {
174     return new Main3();
175   }
176 }
177