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 public class Main {
main(String[] args)18   public static void main(String[] args) throws Exception {
19     System.loadLibrary(args[0]);
20     if (isAotCompiled(Main.class, "hasJit")) {
21       throw new Error("This test must be run with --no-prebuild!");
22     }
23     if (!hasJit()) {
24       return;
25     }
26 
27     testCompilationUseAndCollection();
28     testMixedFramesOnStack();
29   }
30 
testCompilationUseAndCollection()31   public static void testCompilationUseAndCollection() {
32     // Test that callThrough() can be JIT-compiled.
33     assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
34     assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
35     ensureCompiledCallThroughEntrypoint(/* call */ true);
36     assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
37     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
38 
39     // Use callThrough() once again now that the method has a JIT-compiled stub.
40     callThrough(Main.class, "doNothing");
41 
42     // Test that GC with the JIT-compiled stub on the stack does not collect it.
43     // Also tests stack walk over the JIT-compiled stub.
44     callThrough(Main.class, "testGcWithCallThroughStubOnStack");
45 
46     // Test that, when marking used methods before a full JIT GC, a single execution
47     // of the GenericJNI trampoline can save the compiled stub from being collected.
48     testSingleInvocationTriggersRecompilation();
49 
50     // Test that the JNI compiled stub can actually be collected.
51     testStubCanBeCollected();
52   }
53 
testGcWithCallThroughStubOnStack()54   public static void testGcWithCallThroughStubOnStack() {
55     // Check that this method was called via JIT-compiled callThrough() stub.
56     assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
57     // This assertion also exercises stack walk over the JIT-compiled callThrough() stub.
58     assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
59 
60     doJitGcsUntilFullJitGcIsScheduled();
61     // The callThrough() on the stack above this method is using the compiled stub,
62     // so the JIT GC should not remove the compiled code.
63     jitGc();
64     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
65   }
66 
testSingleInvocationTriggersRecompilation()67   public static void testSingleInvocationTriggersRecompilation() {
68     // After scheduling a full JIT GC, single call through the GenericJNI
69     // trampoline should ensure that the compiled stub is used again.
70     doJitGcsUntilFullJitGcIsScheduled();
71     callThrough(Main.class, "doNothing");
72     ensureCompiledCallThroughEntrypoint(/* call */ false);  // Wait for the compilation task to run.
73     assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
74     jitGc();  // This JIT GC should not collect the callThrough() stub.
75     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
76   }
77 
testMixedFramesOnStack()78   public static void testMixedFramesOnStack() {
79     // Starts without a compiled JNI stub for callThrough().
80     assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
81     assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
82     callThrough(Main.class, "testMixedFramesOnStackStage2");
83     // We have just returned through the JIT-compiled JNI stub, so it must still
84     // be compiled (though not necessarily with the entrypoint pointing to it).
85     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
86     // Though the callThrough() is on the stack, that frame is using the GenericJNI
87     // and does not prevent the collection of the JNI stub.
88     testStubCanBeCollected();
89   }
90 
testMixedFramesOnStackStage2()91   public static void testMixedFramesOnStackStage2() {
92     // We cannot assert that callThrough() has no JIT compiled stub as that check
93     // may race against the compilation task. Just check the caller.
94     assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
95     // Now ensure that the JNI stub is compiled and used.
96     ensureCompiledCallThroughEntrypoint(/* call */ true);
97     callThrough(Main.class, "testMixedFramesOnStackStage3");
98   }
99 
testMixedFramesOnStackStage3()100   public static void testMixedFramesOnStackStage3() {
101     // Check that this method was called via JIT-compiled callThrough() stub.
102     assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
103     // This assertion also exercises stack walk over the JIT-compiled callThrough() stub.
104     assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
105     // For a good measure, try a JIT GC.
106     jitGc();
107   }
108 
testStubCanBeCollected()109   public static void testStubCanBeCollected() {
110     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
111     doJitGcsUntilFullJitGcIsScheduled();
112     assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
113     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
114     jitGc();  // JIT GC without callThrough() on the stack should collect the callThrough() stub.
115     assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
116     assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
117   }
118 
doJitGcsUntilFullJitGcIsScheduled()119   public static void doJitGcsUntilFullJitGcIsScheduled() {
120     // We enter with a compiled stub for callThrough() but we also need the entrypoint to be set.
121     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
122     ensureCompiledCallThroughEntrypoint(/* call */ true);
123     // Perform JIT GC until the next GC is marked to do full collection.
124     do {
125       assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
126       callThrough(Main.class, "jitGc");  // JIT GC with callThrough() safely on the stack.
127     } while (!isNextJitGcFull());
128     // The JIT GC before the full collection resets entrypoints and waits to see
129     // if the methods are still in use.
130     assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
131     assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
132   }
133 
ensureCompiledCallThroughEntrypoint(boolean call)134   public static void ensureCompiledCallThroughEntrypoint(boolean call) {
135     int count = 0;
136     while (!hasJitCompiledEntrypoint(Main.class, "callThrough")) {
137       // If `call` is true, also exercise the `callThrough()` method to increase hotness.
138       // Ramp-up the number of calls we do up to 1 << 12.
139       final int rampUpCutOff = 12;
140       int limit = call ? 1 << Math.min(count, rampUpCutOff) : 0;
141       for (int i = 0; i < limit; ++i) {
142         callThrough(Main.class, "doNothing");
143       }
144       try {
145         // Sleep to give a chance for the JIT to compile `callThrough` stub.
146         // After the ramp-up phase, give the JIT even more time to compile.
147         Thread.sleep(count >= rampUpCutOff ? 200 : 100);
148       } catch (Exception e) {
149         // Ignore
150       }
151       if (++count == 50) {
152         throw new Error("TIMEOUT");
153       }
154     };
155   }
156 
assertTrue(boolean value)157   public static void assertTrue(boolean value) {
158     if (!value) {
159       throw new AssertionError("Expected true!");
160     }
161   }
162 
assertFalse(boolean value)163   public static void assertFalse(boolean value) {
164     if (value) {
165       throw new AssertionError("Expected false!");
166     }
167   }
168 
doNothing()169   public static void doNothing() { }
throwError()170   public static void throwError() { throw new Error(); }
171 
172   // Note that the callThrough()'s shorty differs from shorties of the other
173   // native methods used in this test because of the return type `void.`
callThrough(Class<?> cls, String methodName)174   public native static void callThrough(Class<?> cls, String methodName);
175 
jitGc()176   public native static void jitGc();
isNextJitGcFull()177   public native static boolean isNextJitGcFull();
178 
isAotCompiled(Class<?> cls, String methodName)179   public native static boolean isAotCompiled(Class<?> cls, String methodName);
hasJitCompiledEntrypoint(Class<?> cls, String methodName)180   public native static boolean hasJitCompiledEntrypoint(Class<?> cls, String methodName);
hasJitCompiledCode(Class<?> cls, String methodName)181   public native static boolean hasJitCompiledCode(Class<?> cls, String methodName);
hasJit()182   private native static boolean hasJit();
183 }
184