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