/*
|
* Copyright (C) 2017 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.
|
*/
|
|
public class Main {
|
public static void main(String[] args) throws Exception {
|
System.loadLibrary(args[0]);
|
if (isAotCompiled(Main.class, "hasJit")) {
|
throw new Error("This test must be run with --no-prebuild!");
|
}
|
if (!hasJit()) {
|
return;
|
}
|
|
testCompilationUseAndCollection();
|
testMixedFramesOnStack();
|
}
|
|
public static void testCompilationUseAndCollection() {
|
// Test that callThrough() can be JIT-compiled.
|
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
|
ensureCompiledCallThroughEntrypoint(/* call */ true);
|
assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
|
// Use callThrough() once again now that the method has a JIT-compiled stub.
|
callThrough(Main.class, "doNothing");
|
|
// Test that GC with the JIT-compiled stub on the stack does not collect it.
|
// Also tests stack walk over the JIT-compiled stub.
|
callThrough(Main.class, "testGcWithCallThroughStubOnStack");
|
|
// Test that, when marking used methods before a full JIT GC, a single execution
|
// of the GenericJNI trampoline can save the compiled stub from being collected.
|
testSingleInvocationTriggersRecompilation();
|
|
// Test that the JNI compiled stub can actually be collected.
|
testStubCanBeCollected();
|
}
|
|
public static void testGcWithCallThroughStubOnStack() {
|
// Check that this method was called via JIT-compiled callThrough() stub.
|
assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
// This assertion also exercises stack walk over the JIT-compiled callThrough() stub.
|
assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
|
|
doJitGcsUntilFullJitGcIsScheduled();
|
// The callThrough() on the stack above this method is using the compiled stub,
|
// so the JIT GC should not remove the compiled code.
|
jitGc();
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
}
|
|
public static void testSingleInvocationTriggersRecompilation() {
|
// After scheduling a full JIT GC, single call through the GenericJNI
|
// trampoline should ensure that the compiled stub is used again.
|
doJitGcsUntilFullJitGcIsScheduled();
|
callThrough(Main.class, "doNothing");
|
ensureCompiledCallThroughEntrypoint(/* call */ false); // Wait for the compilation task to run.
|
assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
jitGc(); // This JIT GC should not collect the callThrough() stub.
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
}
|
|
public static void testMixedFramesOnStack() {
|
// Starts without a compiled JNI stub for callThrough().
|
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
|
callThrough(Main.class, "testMixedFramesOnStackStage2");
|
// We have just returned through the JIT-compiled JNI stub, so it must still
|
// be compiled (though not necessarily with the entrypoint pointing to it).
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
// Though the callThrough() is on the stack, that frame is using the GenericJNI
|
// and does not prevent the collection of the JNI stub.
|
testStubCanBeCollected();
|
}
|
|
public static void testMixedFramesOnStackStage2() {
|
// We cannot assert that callThrough() has no JIT compiled stub as that check
|
// may race against the compilation task. Just check the caller.
|
assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
|
// Now ensure that the JNI stub is compiled and used.
|
ensureCompiledCallThroughEntrypoint(/* call */ true);
|
callThrough(Main.class, "testMixedFramesOnStackStage3");
|
}
|
|
public static void testMixedFramesOnStackStage3() {
|
// Check that this method was called via JIT-compiled callThrough() stub.
|
assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
// This assertion also exercises stack walk over the JIT-compiled callThrough() stub.
|
assertTrue(new Throwable().getStackTrace()[1].getMethodName().equals("callThrough"));
|
// For a good measure, try a JIT GC.
|
jitGc();
|
}
|
|
public static void testStubCanBeCollected() {
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
doJitGcsUntilFullJitGcIsScheduled();
|
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
jitGc(); // JIT GC without callThrough() on the stack should collect the callThrough() stub.
|
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
|
}
|
|
public static void doJitGcsUntilFullJitGcIsScheduled() {
|
// We enter with a compiled stub for callThrough() but we also need the entrypoint to be set.
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
ensureCompiledCallThroughEntrypoint(/* call */ true);
|
// Perform JIT GC until the next GC is marked to do full collection.
|
do {
|
assertTrue(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
callThrough(Main.class, "jitGc"); // JIT GC with callThrough() safely on the stack.
|
} while (!isNextJitGcFull());
|
// The JIT GC before the full collection resets entrypoints and waits to see
|
// if the methods are still in use.
|
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
|
assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
|
}
|
|
public static void ensureCompiledCallThroughEntrypoint(boolean call) {
|
int count = 0;
|
while (!hasJitCompiledEntrypoint(Main.class, "callThrough")) {
|
// If `call` is true, also exercise the `callThrough()` method to increase hotness.
|
// Ramp-up the number of calls we do up to 1 << 12.
|
final int rampUpCutOff = 12;
|
int limit = call ? 1 << Math.min(count, rampUpCutOff) : 0;
|
for (int i = 0; i < limit; ++i) {
|
callThrough(Main.class, "doNothing");
|
}
|
try {
|
// Sleep to give a chance for the JIT to compile `callThrough` stub.
|
// After the ramp-up phase, give the JIT even more time to compile.
|
Thread.sleep(count >= rampUpCutOff ? 200 : 100);
|
} catch (Exception e) {
|
// Ignore
|
}
|
if (++count == 50) {
|
throw new Error("TIMEOUT");
|
}
|
};
|
}
|
|
public static void assertTrue(boolean value) {
|
if (!value) {
|
throw new AssertionError("Expected true!");
|
}
|
}
|
|
public static void assertFalse(boolean value) {
|
if (value) {
|
throw new AssertionError("Expected false!");
|
}
|
}
|
|
public static void doNothing() { }
|
public static void throwError() { throw new Error(); }
|
|
// Note that the callThrough()'s shorty differs from shorties of the other
|
// native methods used in this test because of the return type `void.`
|
public native static void callThrough(Class<?> cls, String methodName);
|
|
public native static void jitGc();
|
public native static boolean isNextJitGcFull();
|
|
public native static boolean isAotCompiled(Class<?> cls, String methodName);
|
public native static boolean hasJitCompiledEntrypoint(Class<?> cls, String methodName);
|
public native static boolean hasJitCompiledCode(Class<?> cls, String methodName);
|
private native static boolean hasJit();
|
}
|