1 /* 2 * Copyright (C) 2016 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 { getName()18 String getName() { 19 return "Main1"; 20 } 21 printError(String msg)22 void printError(String msg) { 23 System.out.println(msg); 24 } 25 foo(int i)26 void foo(int i) { 27 if (i != 1) { 28 printError("error1"); 29 } 30 } 31 getValue1()32 int getValue1() { 33 return 1; 34 } getValue2()35 int getValue2() { 36 return 2; 37 } getValue3()38 int getValue3() { 39 return 3; 40 } getValue4()41 int getValue4() { 42 return 4; 43 } getValue5()44 int getValue5() { 45 return 5; 46 } getValue6()47 int getValue6() { 48 return 6; 49 } 50 } 51 52 class Main2 extends Main1 { getName()53 String getName() { 54 return "Main2"; 55 } 56 foo(int i)57 void foo(int i) { 58 if (i != 2) { 59 printError("error2"); 60 } 61 } 62 } 63 64 class Main3 extends Main1 { getName()65 String getName() { 66 return "Main3"; 67 } 68 } 69 70 public class Main { 71 static Main1 sMain1; 72 static Main1 sMain2; 73 74 static boolean sIsOptimizing = true; 75 static boolean sHasJIT = true; 76 static volatile boolean sOtherThreadStarted; 77 78 // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. 79 // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. 80 // After Helper.createMain2() which links in Main2, live testOverride() on stack 81 // should be deoptimized. testOverride(boolean createMain2, boolean wait, boolean setHasJIT)82 static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) { 83 if (setHasJIT) { 84 if (isInterpreted()) { 85 sHasJIT = false; 86 } 87 return; 88 } 89 90 if (createMain2 && (sIsOptimizing || sHasJIT)) { 91 assertIsManaged(); 92 } 93 94 sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); 95 96 if (createMain2) { 97 // Wait for the other thread to start. 98 while (!sOtherThreadStarted); 99 // Create an Main2 instance and assign it to sMain2. 100 // sMain1 is kept the same. 101 sMain2 = Helper.createMain2(); 102 // Wake up the other thread. 103 synchronized(Main.class) { 104 Main.class.notify(); 105 } 106 } else if (wait) { 107 // This is the other thread. 108 synchronized(Main.class) { 109 sOtherThreadStarted = true; 110 // Wait for Main2 to be linked and deoptimization is triggered. 111 try { 112 Main.class.wait(); 113 } catch (Exception e) { 114 } 115 } 116 } 117 118 // There should be a deoptimization here right after Main2 is linked by 119 // calling Helper.createMain2(), even though sMain1 didn't change. 120 // The behavior here would be different if inline-cache is used, which 121 // doesn't deoptimize since sMain1 still hits the type cache. 122 sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); 123 if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { 124 // This method should be deoptimized right after Main2 is created. 125 assertIsInterpreted(); 126 } 127 128 if (sMain2 != null) { 129 sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); 130 } 131 } 132 133 static Main1[] sArray; 134 calcValue(Main1 m)135 static long calcValue(Main1 m) { 136 return m.getValue1() 137 + m.getValue2() * 2 138 + m.getValue3() * 3 139 + m.getValue4() * 4 140 + m.getValue5() * 5 141 + m.getValue6() * 6; 142 } 143 testNoOverrideLoop(int count)144 static long testNoOverrideLoop(int count) { 145 long sum = 0; 146 for (int i=0; i<count; i++) { 147 sum += calcValue(sArray[0]); 148 sum += calcValue(sArray[1]); 149 sum += calcValue(sArray[2]); 150 } 151 return sum; 152 } 153 testNoOverride()154 static void testNoOverride() { 155 sArray = new Main1[3]; 156 sArray[0] = new Main1(); 157 sArray[1] = Helper.createMain2(); 158 sArray[2] = Helper.createMain3(); 159 long sum = 0; 160 // Loop enough to get methods JITed. 161 for (int i=0; i<100; i++) { 162 testNoOverrideLoop(1); 163 } 164 ensureJitCompiled(Main.class, "testNoOverrideLoop"); 165 ensureJitCompiled(Main.class, "calcValue"); 166 167 long t1 = System.currentTimeMillis(); 168 sum = testNoOverrideLoop(100000); 169 long t2 = System.currentTimeMillis(); 170 if (sum != 27300000L) { 171 System.out.println("Unexpected result."); 172 } 173 } 174 assertSingleImplementation(Class<?> clazz, String method_name, boolean b)175 private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { 176 if (hasSingleImplementation(clazz, method_name) != b) { 177 System.out.println(clazz + "." + method_name + 178 " doesn't have single implementation value of " + b); 179 } 180 } 181 182 // Test scenarios under which CHA-based devirtualization happens, 183 // and class loading that overrides a method can invalidate compiled code. 184 // Also test pure non-overriding case, which is more for checking generated 185 // code form. main(String[] args)186 public static void main(String[] args) { 187 System.loadLibrary(args[0]); 188 189 // CHeck some boot-image methods. 190 191 // We would want to have this, but currently setting single-implementation in the boot image 192 // does not work well with app images. b/34193647 193 final boolean ARRAYLIST_SIZE_EXPECTED = false; 194 assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED); 195 196 // java.util.LinkedHashMap overrides get(). 197 assertSingleImplementation(java.util.HashMap.class, "get", false); 198 199 // We don't set single-implementation modifier bit for final classes or methods 200 // since we can devirtualize without CHA for those cases. However hasSingleImplementation() 201 // should return true for those cases. 202 assertSingleImplementation(java.lang.String.class, "charAt", true); 203 assertSingleImplementation(java.lang.Thread.class, "join", true); 204 205 if (isInterpreted()) { 206 sIsOptimizing = false; 207 } 208 209 // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. 210 sMain1 = new Main1(); 211 212 ensureJitCompiled(Main.class, "testOverride"); 213 testOverride(false, false, true); 214 215 if (sHasJIT && !sIsOptimizing) { 216 assertSingleImplementation(Main1.class, "foo", true); 217 } else { 218 // Main2 is verified ahead-of-time so it's linked in already. 219 } 220 assertSingleImplementation(Main1.class, "getValue1", true); 221 222 // Create another thread that also calls sMain1.foo(). 223 // Try to test suspend and deopt another thread. 224 new Thread() { 225 public void run() { 226 testOverride(false, true, false); 227 } 228 }.start(); 229 230 // This will create Main2 instance in the middle of testOverride(). 231 testOverride(true, false, false); 232 assertSingleImplementation(Main1.class, "foo", false); 233 assertSingleImplementation(Main1.class, "getValue1", true); 234 235 testNoOverride(); 236 } 237 ensureJitCompiled(Class<?> itf, String method_name)238 private static native void ensureJitCompiled(Class<?> itf, String method_name); assertIsInterpreted()239 private static native void assertIsInterpreted(); assertIsManaged()240 private static native void assertIsManaged(); isInterpreted()241 private static native boolean isInterpreted(); hasSingleImplementation(Class<?> clazz, String method_name)242 private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); 243 } 244 245 // Put createMain2() in another class to avoid class loading due to verifier. 246 class Helper { createMain2()247 static Main1 createMain2() { 248 return new Main2(); 249 } createMain3()250 static Main1 createMain3() { 251 return new Main3(); 252 } 253 } 254