Merge "ART: Add JIT cache race test"
am: d38b67c989

Change-Id: If621024e6fc10ce02f225be18faf5541b92abeed
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 2744c4f..cd386c0 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -688,6 +688,57 @@
   return CodeCacheSizeLocked();
 }
 
+bool JitCodeCache::RemoveMethod(ArtMethod* method, bool release_memory) {
+  MutexLock mu(Thread::Current(), lock_);
+  if (method->IsNative()) {
+    return false;
+  }
+
+  bool in_cache = false;
+  {
+    ScopedCodeCacheWrite ccw(code_map_.get());
+    for (auto code_iter = method_code_map_.begin(); code_iter != method_code_map_.end();) {
+      if (code_iter->second == method) {
+        if (release_memory) {
+          FreeCode(code_iter->first);
+        }
+        code_iter = method_code_map_.erase(code_iter);
+        in_cache = true;
+        continue;
+      }
+      ++code_iter;
+    }
+  }
+
+  bool osr = false;
+  auto code_map = osr_code_map_.find(method);
+  if (code_map != osr_code_map_.end()) {
+    osr_code_map_.erase(code_map);
+    osr = true;
+  }
+
+  if (!in_cache) {
+    return false;
+  }
+
+  ProfilingInfo* info = method->GetProfilingInfo(kRuntimePointerSize);
+  if (info != nullptr) {
+    auto profile = std::find(profiling_infos_.begin(), profiling_infos_.end(), info);
+    DCHECK(profile != profiling_infos_.end());
+    profiling_infos_.erase(profile);
+  }
+  method->SetProfilingInfo(nullptr);
+  method->ClearCounter();
+  Runtime::Current()->GetInstrumentation()->UpdateMethodsCode(
+      method, GetQuickToInterpreterBridge());
+  VLOG(jit)
+      << "JIT removed (osr=" << std::boolalpha << osr << std::noboolalpha << ") "
+      << ArtMethod::PrettyMethod(method) << "@" << method
+      << " ccache_size=" << PrettySize(CodeCacheSizeLocked()) << ": "
+      << " dcache_size=" << PrettySize(DataCacheSizeLocked());
+  return true;
+}
+
 // This notifies the code cache that the given method has been redefined and that it should remove
 // any cached information it has on the method. All threads must be suspended before calling this
 // method. The compiled code for the method (if there is any) must not be in any threads call stack.
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 9ecc876..daa1d61 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -171,6 +171,13 @@
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Removes method from the cache for testing purposes. The caller
+  // must ensure that all threads are suspended and the method should
+  // not be in any thread's stack.
+  bool RemoveMethod(ArtMethod* method, bool release_memory)
+      REQUIRES(!lock_)
+      REQUIRES(Locks::mutator_lock_);
+
   // Remove all methods in our cache that were allocated by 'alloc'.
   void RemoveMethodsIn(Thread* self, const LinearAlloc& alloc)
       REQUIRES(!lock_)
diff --git a/test/708-jit-cache-churn/expected.txt b/test/708-jit-cache-churn/expected.txt
new file mode 100644
index 0000000..77a1486
--- /dev/null
+++ b/test/708-jit-cache-churn/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Done
diff --git a/test/708-jit-cache-churn/info.txt b/test/708-jit-cache-churn/info.txt
new file mode 100644
index 0000000..4aaa3d4
--- /dev/null
+++ b/test/708-jit-cache-churn/info.txt
@@ -0,0 +1 @@
+Tests JIT cache for page permission updates and CPU cache inconsistencies. Only runs when test runner permits JIT, e.g. --jit.
diff --git a/test/708-jit-cache-churn/jit.cc b/test/708-jit-cache-churn/jit.cc
new file mode 100644
index 0000000..1284a87
--- /dev/null
+++ b/test/708-jit-cache-churn/jit.cc
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+#include "art_method.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread_list.h"
+
+namespace art {
+
+extern "C" JNIEXPORT
+jboolean
+Java_JitCacheChurnTest_removeJitCompiledMethod(JNIEnv* env,
+                                               jclass,
+                                               jobject javaMethod,
+                                               jboolean releaseMemory) {
+  if (!Runtime::Current()->UseJitCompilation()) {
+    return JNI_FALSE;
+  }
+
+  jit::Jit* jit = Runtime::Current()->GetJit();
+  jit->WaitForCompilationToFinish(Thread::Current());
+
+  ScopedObjectAccess soa(env);
+  ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod);
+
+  jit::JitCodeCache* code_cache = jit->GetCodeCache();
+
+  // Drop the shared mutator lock
+  ScopedThreadSuspension selfSuspension(Thread::Current(), art::ThreadState::kNative);
+  // Get exclusive mutator lock with suspend all.
+  ScopedSuspendAll suspend("Removing JIT compiled method", /*long_suspend*/true);
+  bool removed = code_cache->RemoveMethod(method, static_cast<bool>(releaseMemory));
+  return removed ? JNI_TRUE : JNI_FALSE;
+}
+
+}  // namespace art
diff --git a/test/708-jit-cache-churn/src/JitCacheChurnTest.java b/test/708-jit-cache-churn/src/JitCacheChurnTest.java
new file mode 100644
index 0000000..abc5f35
--- /dev/null
+++ b/test/708-jit-cache-churn/src/JitCacheChurnTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A test driver for JIT compiling methods and looking for JIT
+ * cache issues.
+ */
+public class JitCacheChurnTest {
+  /* The name of methods to JIT */
+  private static final String JITTED_METHOD = "$noinline$Call";
+
+  /* The number of cores to oversubscribe load by. */
+  private static final int OVERSUBSCRIBED_CORES = 1;
+
+  /* The number of concurrent executions of methods to be JIT compiled. */
+  private static final int CONCURRENCY =
+      Runtime.getRuntime().availableProcessors() + OVERSUBSCRIBED_CORES;
+
+  /* The number of times the methods to be JIT compiled should be executed per thread. */
+  private static final int METHOD_ITERATIONS = 10;
+
+  /* Number of test iterations JIT methods and removing methods from JIT cache. */
+  private static final int TEST_ITERATIONS = 512;
+
+  /* Tasks to run and generate compiled code of various sizes */
+  private static final BaseTask [] TASKS = {
+    new TaskOne(), new TaskTwo(), new TaskThree(), new TaskFour(), new TaskFive(), new TaskSix(),
+    new TaskSeven(), new TaskEight(), new TaskNine(), new TaskTen()
+  };
+  private static final int TASK_BITMASK = (1 << TASKS.length) - 1;
+
+  private final ExecutorService executorService;
+  private int runMask = 0;
+
+  private JitCacheChurnTest() {
+    this.executorService = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 5000,
+        TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
+  }
+
+  private void shutdown() {
+    this.executorService.shutdown();
+  }
+
+  private void runTasks(Callable<Integer> task) {
+    // Force JIT compilation of tasks method.
+    ensureJitCompiled(task.getClass(), JITTED_METHOD);
+
+    // Launch worker threads to run JIT compiled method.
+    try {
+      ArrayList<Callable<Integer>> tasks = new ArrayList<>(CONCURRENCY);
+      for (int i = 0; i < CONCURRENCY; ++i) {
+        tasks.add(i, task);
+      }
+
+      List<Future<Integer>> results = executorService.invokeAll(tasks);
+      for (Future<?> result : results) {
+        result.get();
+      }
+    } catch (InterruptedException | ExecutionException e) {
+      System.err.println(e);
+      System.exit(-1);
+    }
+  }
+
+  private static abstract class BaseTask implements Callable<Integer> {
+    private static CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY);
+
+    public Integer call() throws Exception {
+      barrier.await();
+      int iterations = METHOD_ITERATIONS + 1;
+      for (int i = 0; i < iterations; ++i) {
+        $noinline$Call();
+      }
+      return $noinline$Call();
+    }
+
+    protected abstract Integer $noinline$Call();
+  }
+
+  private static class TaskOne extends BaseTask {
+    @Override
+    protected Integer $noinline$Call() {
+      return null;
+    }
+  }
+
+  private static class TaskTwo extends BaseTask {
+    @Override
+    protected Integer $noinline$Call() {
+      return 0;
+    }
+  }
+
+  private static class TaskThree extends BaseTask {
+    @Override
+    protected Integer $noinline$Call() {
+      int sum = 0;
+      for (int i = 0; i < 3; ++i) {
+        sum = i * (i + 1);
+      }
+      return sum;
+    }
+  }
+
+  private static class TaskFour extends BaseTask {
+    @Override
+    protected Integer $noinline$Call() {
+      int sum = 0;
+      for (int i = 0; i < 10; ++i) {
+        int bits = i;
+        bits = ((bits >>> 1) & 0x55555555) | ((bits << 1) & 0x55555555);
+        bits = ((bits >>> 2) & 0x33333333) | ((bits << 2) & 0x33333333);
+        bits = ((bits >>> 4) & 0x0f0f0f0f) | ((bits << 4) & 0x0f0f0f0f);
+        bits = ((bits >>> 8) & 0x00ff00ff) | ((bits << 8) & 0x00ff00ff);
+        bits = (bits >>> 16) | (bits << 16);
+        sum += bits;
+      }
+      return sum;
+    }
+  }
+
+  private static class TaskFive extends BaseTask {
+    static final AtomicInteger instances = new AtomicInteger(0);
+    int instance;
+    TaskFive() {
+      instance = instances.getAndIncrement();
+    }
+    protected Integer $noinline$Call() {
+      return instance;
+    }
+  }
+
+  private static class TaskSix extends TaskFive {
+    protected Integer $noinline$Call() {
+      return instance + 1;
+    }
+  }
+
+  private static class TaskSeven extends TaskFive {
+    protected Integer $noinline$Call() {
+      return 2 * instance + 1;
+    }
+  }
+
+  private static class TaskEight extends TaskFive {
+    protected Integer $noinline$Call() {
+      double a = Math.cosh(2.22 * instance);
+      double b = a / 2;
+      double c = b * 3;
+      double d = a + b + c;
+      if (d > 42) {
+        d *= Math.max(Math.sin(d), Math.sinh(d));
+        d *= Math.max(1.33, 0.17 * Math.sinh(d));
+        d *= Math.max(1.34, 0.21 * Math.sinh(d));
+        d *= Math.max(1.35, 0.32 * Math.sinh(d));
+        d *= Math.max(1.36, 0.41 * Math.sinh(d));
+        d *= Math.max(1.37, 0.57 * Math.sinh(d));
+        d *= Math.max(1.38, 0.61 * Math.sinh(d));
+        d *= Math.max(1.39, 0.79 * Math.sinh(d));
+        d += Double.parseDouble("3.711e23");
+      }
+
+      if (d > 3) {
+        return (int) a;
+      } else {
+        return (int) b;
+      }
+    }
+  }
+
+  private static class TaskNine extends TaskFive {
+    private final String [] numbers = { "One", "Two", "Three", "Four", "Five", "Six" };
+
+    protected Integer $noinline$Call() {
+      String number = numbers[instance % numbers.length];
+      return number.length();
+    }
+  }
+
+  private static class TaskTen extends TaskFive {
+    private final String [] numbers = { "12345", "23451", "34512", "78901", "89012" };
+
+    protected Integer $noinline$Call() {
+      int odd = 0;
+      String number = numbers[instance % numbers.length];
+      for (int i = 0; i < number.length(); i += 2) {
+        odd += Integer.parseInt(numbers[i]);
+      }
+      odd *= 3;
+
+      int even = 0;
+      for (int i = 1; i < number.length(); i += 2) {
+        even += Integer.parseInt(numbers[i]);
+      }
+      return (odd + even) % 10;
+    }
+  }
+
+  private void runAndJitMethods(int mask) {
+    runMask |= mask;
+    for (int index = 0; mask != 0; mask >>= 1, index++) {
+      if ((mask & 1) == 1) {
+        runTasks(TASKS[index]);
+      }
+    }
+  }
+
+  private static void ensureJitCompiled(Class<?> klass, String name) {
+    Main.ensureJitCompiled(klass, name);
+  }
+
+  private void removeJittedMethod(Class<?> klass, String name) {
+    Method method = null;
+    try {
+      method = klass.getDeclaredMethod(name);
+    } catch (NoSuchMethodException e) {
+      System.err.println(e);
+      System.exit(-1);
+    }
+    removeJitCompiledMethod(method, false);
+  }
+
+  private void removeJittedMethods(int mask) {
+    mask = mask & runMask;
+    runMask ^= mask;
+    for (int index = 0; mask != 0; mask >>= 1, index++) {
+      if ((mask & 1) == 1) {
+        removeJittedMethod(TASKS[index].getClass(), JITTED_METHOD);
+      }
+    }
+  }
+
+  private static int getMethodsAsMask(Random rng) {
+    return rng.nextInt(TASK_BITMASK) + 1;
+  }
+
+  public static void run() {
+    JitCacheChurnTest concurrentExecution = new JitCacheChurnTest();
+    Random invokeMethodGenerator = new Random(5);
+    Random removeMethodGenerator = new Random(7);
+    try {
+      for (int i = 0; i < TEST_ITERATIONS; ++i) {
+        concurrentExecution.runAndJitMethods(getMethodsAsMask(invokeMethodGenerator));
+        concurrentExecution.removeJittedMethods(getMethodsAsMask(removeMethodGenerator));
+      }
+    } finally {
+      concurrentExecution.shutdown();
+    }
+  }
+
+  private static native void removeJitCompiledMethod(Method method, boolean releaseMemory);
+}
diff --git a/test/708-jit-cache-churn/src/Main.java b/test/708-jit-cache-churn/src/Main.java
new file mode 100644
index 0000000..0595aae
--- /dev/null
+++ b/test/708-jit-cache-churn/src/Main.java
@@ -0,0 +1,31 @@
+/*
+ * 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 {
+    // Explicit loadLibrary here to pull JNI exports from arttestd.
+    System.loadLibrary(args[0]);
+    if (hasJit()) {
+      JitCacheChurnTest.run();
+    }
+    System.out.println("Done");
+  }
+
+  static native boolean hasJit();
+
+  static native void ensureJitCompiled(Class<?> klass, String methodName);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 35c3d9c..0937c62 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -394,6 +394,7 @@
         "626-const-class-linking/clear_dex_cache_types.cc",
         "642-fp-callees/fp_callees.cc",
         "647-jni-get-field-id/get_field_id.cc",
+        "708-jit-cache-churn/jit.cc"
     ],
     shared_libs: [
         "libbacktrace",