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