/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class Main1 { String getName() { return "Main1"; } void printError(String msg) { System.out.println(msg); } void foo(int i) { if (i != 1) { printError("error1"); } } int getValue1() { return 1; } int getValue2() { return 2; } int getValue3() { return 3; } int getValue4() { return 4; } int getValue5() { return 5; } int getValue6() { return 6; } } class Main2 extends Main1 { String getName() { return "Main2"; } void foo(int i) { if (i != 2) { printError("error2"); } } } class Main3 extends Main1 { String getName() { return "Main3"; } } public class Main { static Main1 sMain1; static Main1 sMain2; static boolean sIsOptimizing = true; static boolean sHasJIT = true; static volatile boolean sOtherThreadStarted; // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. // After Dummy.createMain2() which links in Main2, live testOverride() on stack // should be deoptimized. static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) { if (setHasJIT) { if (isInterpreted()) { sHasJIT = false; } return; } if (createMain2 && (sIsOptimizing || sHasJIT)) { assertIsManaged(); } sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); if (createMain2) { // Wait for the other thread to start. while (!sOtherThreadStarted); // Create an Main2 instance and assign it to sMain2. // sMain1 is kept the same. sMain2 = Dummy.createMain2(); // Wake up the other thread. synchronized(Main.class) { Main.class.notify(); } } else if (wait) { // This is the other thread. synchronized(Main.class) { sOtherThreadStarted = true; // Wait for Main2 to be linked and deoptimization is triggered. try { Main.class.wait(); } catch (Exception e) { } } } // There should be a deoptimization here right after Main2 is linked by // calling Dummy.createMain2(), even though sMain1 didn't change. // The behavior here would be different if inline-cache is used, which // doesn't deoptimize since sMain1 still hits the type cache. sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { // This method should be deoptimized right after Main2 is created. assertIsInterpreted(); } if (sMain2 != null) { sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); } } static Main1[] sArray; static long calcValue(Main1 m) { return m.getValue1() + m.getValue2() * 2 + m.getValue3() * 3 + m.getValue4() * 4 + m.getValue5() * 5 + m.getValue6() * 6; } static long testNoOverrideLoop(int count) { long sum = 0; for (int i=0; i<count; i++) { sum += calcValue(sArray[0]); sum += calcValue(sArray[1]); sum += calcValue(sArray[2]); } return sum; } static void testNoOverride() { sArray = new Main1[3]; sArray[0] = new Main1(); sArray[1] = Dummy.createMain2(); sArray[2] = Dummy.createMain3(); long sum = 0; // Loop enough to get methods JITed. for (int i=0; i<100; i++) { testNoOverrideLoop(1); } ensureJitCompiled(Main.class, "testNoOverrideLoop"); ensureJitCompiled(Main.class, "calcValue"); long t1 = System.currentTimeMillis(); sum = testNoOverrideLoop(100000); long t2 = System.currentTimeMillis(); if (sum != 27300000L) { System.out.println("Unexpected result."); } } private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { if (hasSingleImplementation(clazz, method_name) != b) { System.out.println(clazz + "." + method_name + " doesn't have single implementation value of " + b); } } // Test scenarios under which CHA-based devirtualization happens, // and class loading that overrides a method can invalidate compiled code. // Also test pure non-overriding case, which is more for checking generated // code form. public static void main(String[] args) { System.loadLibrary(args[0]); // CHeck some boot-image methods. // We would want to have this, but currently setting single-implementation in the boot image // does not work well with app images. b/34193647 final boolean ARRAYLIST_SIZE_EXPECTED = false; assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED); // java.util.LinkedHashMap overrides get(). assertSingleImplementation(java.util.HashMap.class, "get", false); // We don't set single-implementation modifier bit for final classes or methods // since we can devirtualize without CHA for those cases. However hasSingleImplementation() // should return true for those cases. assertSingleImplementation(java.lang.String.class, "charAt", true); assertSingleImplementation(java.lang.Thread.class, "join", true); if (isInterpreted()) { sIsOptimizing = false; } // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. sMain1 = new Main1(); ensureJitCompiled(Main.class, "testOverride"); testOverride(false, false, true); if (sHasJIT && !sIsOptimizing) { assertSingleImplementation(Main1.class, "foo", true); } else { // Main2 is verified ahead-of-time so it's linked in already. } assertSingleImplementation(Main1.class, "getValue1", true); // Create another thread that also calls sMain1.foo(). // Try to test suspend and deopt another thread. new Thread() { public void run() { testOverride(false, true, false); } }.start(); // This will create Main2 instance in the middle of testOverride(). testOverride(true, false, false); assertSingleImplementation(Main1.class, "foo", false); assertSingleImplementation(Main1.class, "getValue1", true); testNoOverride(); } private static native void ensureJitCompiled(Class<?> itf, String method_name); private static native void assertIsInterpreted(); private static native void assertIsManaged(); private static native boolean isInterpreted(); private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); } // Put createMain2() in another class to avoid class loading due to verifier. class Dummy { static Main1 createMain2() { return new Main2(); } static Main1 createMain3() { return new Main3(); } }