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
17class Main1 implements Base {
18}
19
20class Main2 extends Main1 {
21  public void foobar() {}
22}
23
24class Main3 implements Base {
25  public int foo(int i) {
26    if (i != 3) {
27      printError("error3");
28    }
29    return -(i + 10);
30  }
31}
32
33public 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
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
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 Dummy.createMain3() which links in Main3, live testImplement() on stack
59  // should be deoptimized.
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 = Dummy.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 Dummy.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.
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
164  private static native void ensureJitCompiled(Class<?> itf, String method_name);
165  private static native void assertIsInterpreted();
166  private static native void assertIsManaged();
167  private static native boolean isInterpreted();
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.
172class Dummy {
173  static Base createMain3() {
174    return new Main3();
175  }
176}
177