/* * 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(); }