Home | History | Annotate | Download | only in src
      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 {
     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 --no-dex2oat!");
     22     }
     23     if (!hasJit()) {
     24       return;
     25     }
     26 
     27     testCompilationUseAndCollection();
     28     testMixedFramesOnStack();
     29   }
     30 
     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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    157   public static void assertTrue(boolean value) {
    158     if (!value) {
    159       throw new AssertionError("Expected true!");
    160     }
    161   }
    162 
    163   public static void assertFalse(boolean value) {
    164     if (value) {
    165       throw new AssertionError("Expected false!");
    166     }
    167   }
    168 
    169   public static void doNothing() { }
    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.`
    174   public native static void callThrough(Class<?> cls, String methodName);
    175 
    176   public native static void jitGc();
    177   public native static boolean isNextJitGcFull();
    178 
    179   public native static boolean isAotCompiled(Class<?> cls, String methodName);
    180   public native static boolean hasJitCompiledEntrypoint(Class<?> cls, String methodName);
    181   public native static boolean hasJitCompiledCode(Class<?> cls, String methodName);
    182   private native static boolean hasJit();
    183 }
    184