diff options
-rw-r--r-- | runtime/jit/jit_code_cache.cc | 51 | ||||
-rw-r--r-- | runtime/jit/jit_code_cache.h | 7 | ||||
-rw-r--r-- | test/708-jit-cache-churn/expected.txt | 2 | ||||
-rw-r--r-- | test/708-jit-cache-churn/info.txt | 1 | ||||
-rw-r--r-- | test/708-jit-cache-churn/jit.cc | 56 | ||||
-rw-r--r-- | test/708-jit-cache-churn/src/JitCacheChurnTest.java | 279 | ||||
-rw-r--r-- | test/708-jit-cache-churn/src/Main.java | 31 | ||||
-rw-r--r-- | test/Android.bp | 1 |
8 files changed, 428 insertions, 0 deletions
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 2744c4f24a..cd386c06fa 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -688,6 +688,57 @@ size_t JitCodeCache::CodeCacheSize() { 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 9ecc876716..daa1d616a6 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -171,6 +171,13 @@ class JitCodeCache { 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 0000000000..77a1486479 --- /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 0000000000..4aaa3d48c9 --- /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 0000000000..1284a8703d --- /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 0000000000..abc5f35f70 --- /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 0000000000..0595aae506 --- /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 35c3d9c332..0937c62469 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -394,6 +394,7 @@ cc_defaults { "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", |