ART: Add JIT cache race test

Adds a test for the JIT code cache that attempts to trap races between
threads generating code and executing it.

Bug: 38417984
Test: run-test --jit 707
Change-Id: I408b2680b1d266ebe624d6e39113f0261d538e8a
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 599b011..d9b3091 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -393,6 +393,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",