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",