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 interface Iface {
foo(int i)18   public void foo(int i);
19 }
20 
21 abstract class Base implements Iface {
22   // Iface.foo(int) will be added as a miranda method.
23 
printError(String msg)24   void printError(String msg) {
25     System.out.println(msg);
26   }
27 }
28 
29 class Main1 extends Base {
foo(int i)30   public void foo(int i) {
31     if (i != 1) {
32       printError("error1");
33     }
34   }
35 }
36 
37 class Main2 extends Main1 {
foo(int i)38   public void foo(int i) {
39     if (i != 2) {
40       printError("error2");
41     }
42   }
43 }
44 
45 public class Main {
46   static Base sMain1;
47   static Base sMain2;
48 
49   static boolean sIsOptimizing = true;
50   static boolean sHasJIT = true;
51   static volatile boolean sOtherThreadStarted;
52 
assertSingleImplementation(Class<?> clazz, String method_name, boolean b)53   private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
54     if (hasSingleImplementation(clazz, method_name) != b) {
55       System.out.println(clazz + "." + method_name +
56           " doesn't have single implementation value of " + b);
57     }
58   }
59 
60   // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
61   // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
62   // After Dummy.createMain2() which links in Main2, live testOverride() on stack
63   // should be deoptimized.
testOverride(boolean createMain2, boolean wait, boolean setHasJIT)64   static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
65     if (setHasJIT) {
66       if (isInterpreted()) {
67         sHasJIT = false;
68       }
69       return;
70     }
71 
72     if (createMain2 && (sIsOptimizing || sHasJIT)) {
73       assertIsManaged();
74     }
75 
76     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
77 
78     if (createMain2) {
79       // Wait for the other thread to start.
80       while (!sOtherThreadStarted);
81       // Create an Main2 instance and assign it to sMain2.
82       // sMain1 is kept the same.
83       sMain2 = Dummy.createMain2();
84       // Wake up the other thread.
85       synchronized(Main.class) {
86         Main.class.notify();
87       }
88     } else if (wait) {
89       // This is the other thread.
90       synchronized(Main.class) {
91         sOtherThreadStarted = true;
92         // Wait for Main2 to be linked and deoptimization is triggered.
93         try {
94           Main.class.wait();
95         } catch (Exception e) {
96         }
97       }
98     }
99 
100     // There should be a deoptimization here right after Main2 is linked by
101     // calling Dummy.createMain2(), even though sMain1 didn't change.
102     // The behavior here would be different if inline-cache is used, which
103     // doesn't deoptimize since sMain1 still hits the type cache.
104     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
105     if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
106       // This method should be deoptimized right after Main2 is created.
107       assertIsInterpreted();
108     }
109 
110     if (sMain2 != null) {
111       sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
112     }
113   }
114 
115   // Test scenarios under which CHA-based devirtualization happens,
116   // and class loading that overrides a method can invalidate compiled code.
main(String[] args)117   public static void main(String[] args) {
118     System.loadLibrary(args[0]);
119 
120     if (isInterpreted()) {
121       sIsOptimizing = false;
122     }
123 
124     // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
125     sMain1 = new Main1();
126 
127     ensureJitCompiled(Main.class, "testOverride");
128     testOverride(false, false, true);
129 
130     if (sHasJIT && !sIsOptimizing) {
131       assertSingleImplementation(Base.class, "foo", true);
132       assertSingleImplementation(Main1.class, "foo", true);
133     } else {
134       // Main2 is verified ahead-of-time so it's linked in already.
135     }
136 
137     // Create another thread that also calls sMain1.foo().
138     // Try to test suspend and deopt another thread.
139     new Thread() {
140       public void run() {
141         testOverride(false, true, false);
142       }
143     }.start();
144 
145     // This will create Main2 instance in the middle of testOverride().
146     testOverride(true, false, false);
147     assertSingleImplementation(Base.class, "foo", false);
148     assertSingleImplementation(Main1.class, "foo", false);
149   }
150 
ensureJitCompiled(Class<?> itf, String method_name)151   private static native void ensureJitCompiled(Class<?> itf, String method_name);
assertIsInterpreted()152   private static native void assertIsInterpreted();
assertIsManaged()153   private static native void assertIsManaged();
isInterpreted()154   private static native boolean isInterpreted();
hasSingleImplementation(Class<?> clazz, String method_name)155   private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
156 }
157 
158 // Put createMain2() in another class to avoid class loading due to verifier.
159 class Dummy {
createMain2()160   static Main1 createMain2() {
161     return new Main2();
162   }
163 }
164