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 import java.lang.reflect.Method;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Random;
21 import java.util.concurrent.atomic.AtomicInteger;
22 import java.util.concurrent.Callable;
23 import java.util.concurrent.CyclicBarrier;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.Future;
27 import java.util.concurrent.LinkedBlockingQueue;
28 import java.util.concurrent.ThreadPoolExecutor;
29 import java.util.concurrent.TimeUnit;
30 
31 /**
32  * A test driver for JIT compiling methods and looking for JIT
33  * cache issues.
34  */
35 public class JitCacheChurnTest {
36   /* The name of methods to JIT */
37   private static final String JITTED_METHOD = "$noinline$Call";
38 
39   /* The number of cores to oversubscribe load by. */
40   private static final int OVERSUBSCRIBED_CORES = 1;
41 
42   /* The number of concurrent executions of methods to be JIT compiled. */
43   private static final int CONCURRENCY =
44       Runtime.getRuntime().availableProcessors() + OVERSUBSCRIBED_CORES;
45 
46   /* The number of times the methods to be JIT compiled should be executed per thread. */
47   private static final int METHOD_ITERATIONS = 10;
48 
49   /* Number of test iterations JIT methods and removing methods from JIT cache. */
50   private static final int TEST_ITERATIONS = 512;
51 
52   /* Tasks to run and generate compiled code of various sizes */
53   private static final BaseTask [] TASKS = {
54     new TaskOne(), new TaskTwo(), new TaskThree(), new TaskFour(), new TaskFive(), new TaskSix(),
55     new TaskSeven(), new TaskEight(), new TaskNine(), new TaskTen()
56   };
57   private static final int TASK_BITMASK = (1 << TASKS.length) - 1;
58 
59   private final ExecutorService executorService;
60   private int runMask = 0;
61 
JitCacheChurnTest()62   private JitCacheChurnTest() {
63     this.executorService = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 5000,
64         TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
65   }
66 
shutdown()67   private void shutdown() {
68     this.executorService.shutdown();
69   }
70 
runTasks(Callable<Integer> task)71   private void runTasks(Callable<Integer> task) {
72     // Force JIT compilation of tasks method.
73     ensureJitCompiled(task.getClass(), JITTED_METHOD);
74 
75     // Launch worker threads to run JIT compiled method.
76     try {
77       ArrayList<Callable<Integer>> tasks = new ArrayList<>(CONCURRENCY);
78       for (int i = 0; i < CONCURRENCY; ++i) {
79         tasks.add(i, task);
80       }
81 
82       List<Future<Integer>> results = executorService.invokeAll(tasks);
83       for (Future<?> result : results) {
84         result.get();
85       }
86     } catch (InterruptedException | ExecutionException e) {
87       System.err.println(e);
88       System.exit(-1);
89     }
90   }
91 
92   private static abstract class BaseTask implements Callable<Integer> {
93     private static CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY);
94 
call()95     public Integer call() throws Exception {
96       barrier.await();
97       int iterations = METHOD_ITERATIONS + 1;
98       for (int i = 0; i < iterations; ++i) {
99         $noinline$Call();
100       }
101       return $noinline$Call();
102     }
103 
$noinline$Call()104     protected abstract Integer $noinline$Call();
105   }
106 
107   private static class TaskOne extends BaseTask {
108     @Override
$noinline$Call()109     protected Integer $noinline$Call() {
110       return null;
111     }
112   }
113 
114   private static class TaskTwo extends BaseTask {
115     @Override
$noinline$Call()116     protected Integer $noinline$Call() {
117       return 0;
118     }
119   }
120 
121   private static class TaskThree extends BaseTask {
122     @Override
$noinline$Call()123     protected Integer $noinline$Call() {
124       int sum = 0;
125       for (int i = 0; i < 3; ++i) {
126         sum = i * (i + 1);
127       }
128       return sum;
129     }
130   }
131 
132   private static class TaskFour extends BaseTask {
133     @Override
$noinline$Call()134     protected Integer $noinline$Call() {
135       int sum = 0;
136       for (int i = 0; i < 10; ++i) {
137         int bits = i;
138         bits = ((bits >>> 1) & 0x55555555) | ((bits << 1) & 0x55555555);
139         bits = ((bits >>> 2) & 0x33333333) | ((bits << 2) & 0x33333333);
140         bits = ((bits >>> 4) & 0x0f0f0f0f) | ((bits << 4) & 0x0f0f0f0f);
141         bits = ((bits >>> 8) & 0x00ff00ff) | ((bits << 8) & 0x00ff00ff);
142         bits = (bits >>> 16) | (bits << 16);
143         sum += bits;
144       }
145       return sum;
146     }
147   }
148 
149   private static class TaskFive extends BaseTask {
150     static final AtomicInteger instances = new AtomicInteger(0);
151     int instance;
TaskFive()152     TaskFive() {
153       instance = instances.getAndIncrement();
154     }
$noinline$Call()155     protected Integer $noinline$Call() {
156       return instance;
157     }
158   }
159 
160   private static class TaskSix extends TaskFive {
$noinline$Call()161     protected Integer $noinline$Call() {
162       return instance + 1;
163     }
164   }
165 
166   private static class TaskSeven extends TaskFive {
$noinline$Call()167     protected Integer $noinline$Call() {
168       return 2 * instance + 1;
169     }
170   }
171 
172   private static class TaskEight extends TaskFive {
$noinline$Call()173     protected Integer $noinline$Call() {
174       double a = Math.cosh(2.22 * instance);
175       double b = a / 2;
176       double c = b * 3;
177       double d = a + b + c;
178       if (d > 42) {
179         d *= Math.max(Math.sin(d), Math.sinh(d));
180         d *= Math.max(1.33, 0.17 * Math.sinh(d));
181         d *= Math.max(1.34, 0.21 * Math.sinh(d));
182         d *= Math.max(1.35, 0.32 * Math.sinh(d));
183         d *= Math.max(1.36, 0.41 * Math.sinh(d));
184         d *= Math.max(1.37, 0.57 * Math.sinh(d));
185         d *= Math.max(1.38, 0.61 * Math.sinh(d));
186         d *= Math.max(1.39, 0.79 * Math.sinh(d));
187         d += Double.parseDouble("3.711e23");
188       }
189 
190       if (d > 3) {
191         return (int) a;
192       } else {
193         return (int) b;
194       }
195     }
196   }
197 
198   private static class TaskNine extends TaskFive {
199     private final String [] numbers = { "One", "Two", "Three", "Four", "Five", "Six" };
200 
$noinline$Call()201     protected Integer $noinline$Call() {
202       String number = numbers[instance % numbers.length];
203       return number.length();
204     }
205   }
206 
207   private static class TaskTen extends TaskFive {
208     private final String [] numbers = { "12345", "23451", "34512", "78901", "89012" };
209 
$noinline$Call()210     protected Integer $noinline$Call() {
211       int odd = 0;
212       String number = numbers[instance % numbers.length];
213       for (int i = 0; i < number.length(); i += 2) {
214         odd += Integer.parseInt(numbers[i]);
215       }
216       odd *= 3;
217 
218       int even = 0;
219       for (int i = 1; i < number.length(); i += 2) {
220         even += Integer.parseInt(numbers[i]);
221       }
222       return (odd + even) % 10;
223     }
224   }
225 
runAndJitMethods(int mask)226   private void runAndJitMethods(int mask) {
227     runMask |= mask;
228     for (int index = 0; mask != 0; mask >>= 1, index++) {
229       if ((mask & 1) == 1) {
230         runTasks(TASKS[index]);
231       }
232     }
233   }
234 
ensureJitCompiled(Class<?> klass, String name)235   private static void ensureJitCompiled(Class<?> klass, String name) {
236     Main.ensureJitCompiled(klass, name);
237   }
238 
removeJittedMethod(Class<?> klass, String name)239   private void removeJittedMethod(Class<?> klass, String name) {
240     Method method = null;
241     try {
242       method = klass.getDeclaredMethod(name);
243     } catch (NoSuchMethodException e) {
244       System.err.println(e);
245       System.exit(-1);
246     }
247     removeJitCompiledMethod(method, false);
248   }
249 
removeJittedMethods(int mask)250   private void removeJittedMethods(int mask) {
251     mask = mask & runMask;
252     runMask ^= mask;
253     for (int index = 0; mask != 0; mask >>= 1, index++) {
254       if ((mask & 1) == 1) {
255         removeJittedMethod(TASKS[index].getClass(), JITTED_METHOD);
256       }
257     }
258   }
259 
getMethodsAsMask(Random rng)260   private static int getMethodsAsMask(Random rng) {
261     return rng.nextInt(TASK_BITMASK) + 1;
262   }
263 
run()264   public static void run() {
265     JitCacheChurnTest concurrentExecution = new JitCacheChurnTest();
266     Random invokeMethodGenerator = new Random(5);
267     Random removeMethodGenerator = new Random(7);
268     try {
269       for (int i = 0; i < TEST_ITERATIONS; ++i) {
270         concurrentExecution.runAndJitMethods(getMethodsAsMask(invokeMethodGenerator));
271         concurrentExecution.removeJittedMethods(getMethodsAsMask(removeMethodGenerator));
272       }
273     } finally {
274       concurrentExecution.shutdown();
275     }
276   }
277 
removeJitCompiledMethod(Method method, boolean releaseMemory)278   private static native void removeJitCompiledMethod(Method method, boolean releaseMemory);
279 }
280