1 /*
2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package vm.mlvm.indy.share;
25 
26 import java.lang.invoke.CallSite;
27 import java.lang.invoke.MethodHandle;
28 import java.lang.invoke.MethodHandles;
29 import java.lang.invoke.MethodType;
30 import java.util.concurrent.BrokenBarrierException;
31 import java.util.concurrent.CyclicBarrier;
32 
33 import nsk.share.test.Stresser;
34 import vm.mlvm.share.Env;
35 import vm.mlvm.share.MlvmTest;
36 import vm.share.options.Option;
37 
38 /**
39  * The test creates a call site (the call site is supplied by subclass)
40  * and relinks it from one thread while calling the current
41  * target from the other one. Currently there are 6 targets.
42 
43  * The test verifies that target changes in the call site are eventually seen by target calling
44  * thread by measuring a frequency of calls for each target and comparing it with theoretical frequency.
45  *
46  */
47 public abstract class INDIFY_RelinkCallSiteFreqTest extends MlvmTest {
48     private static final int MEASUREMENT_THREADS = Math.max(2, Runtime.getRuntime().availableProcessors() - 1);
49     private static final double MAX_FREQ_DIFFERENCE = 0.01;
50     private static CallSite cs;
51 
52     private volatile boolean testDone = false;
53 
54     @Option(name = "iterations", default_value = "100000", description = "Iterations, each iteration does call site relinking")
55     private int iterations = 100_000;
56 
57     /**
58      * Provides a call site to test.
59      * @param mh MethodHandle to link the new call site to
60      * @return CallSite A new call site linked to MethodHandle supplied in the argument
61      */
createCallSite(MethodHandle mh)62     protected abstract CallSite createCallSite(MethodHandle mh);
63 
64     @Override
run()65     public boolean run() throws Throwable {
66         // Create targets and call site
67         Target[] targets = Target.createTargets(MethodHandles.lookup(), this);
68         // TODO: find a way to make cs a non-static field
69         cs = createCallSite(targets[0].mh);
70 
71         // Call BSM
72         indyWrapper();
73 
74         // Start call site altering thread
75         final FreqMeasurementThread[] csaThread = new FreqMeasurementThread[MEASUREMENT_THREADS];
76         final CyclicBarrier startBarrier = new CyclicBarrier(MEASUREMENT_THREADS + 1);
77         for (int i = 0; i < MEASUREMENT_THREADS; ++i) {
78             csaThread[i] = new FreqMeasurementThread(startBarrier, this, targets.length);
79             csaThread[i].start();
80         }
81 
82         // Start calling invokedynamic
83         Stresser stresser = createStresser();
84         stresser.start(iterations);
85         try {
86             int curTarget = 0;
87             startBarrier.await();
88 
89             while (stresser.continueExecution()) {
90                 stresser.iteration();
91 
92                 Env.traceDebug("Setting new target: " + curTarget);
93                 targets[curTarget].run(cs);
94                 if (++curTarget >= targets.length) {
95                     curTarget = 0;
96                 }
97             }
98 
99         } finally {
100             stresser.finish();
101             testDone = true;
102         }
103 
104         long totalCalls = 0L;
105         long[] callHistogram = new long[targets.length];
106         for (int i = 0; i < csaThread.length; ++i) {
107             csaThread[i].join();
108             totalCalls += csaThread[i].totalCalls;
109             long[] threadCallHistogram = csaThread[i].callHistogram;
110             assert threadCallHistogram.length == callHistogram.length;
111             for (int t = 0; t < callHistogram.length; ++t) {
112                 callHistogram[t] += threadCallHistogram[t];
113             }
114         }
115 
116         Env.traceNormal("Targets called " + totalCalls + " times");
117 
118         for (int i = 0; i < callHistogram.length; ++i) {
119             float measuredFreq = (float) callHistogram[i] / totalCalls;
120             float theoreticalFreq = (float) targets[i].delay / Target.TOTAL_DELAY;
121 
122             boolean freqIsOK =  Math.abs(measuredFreq - theoreticalFreq) < MAX_FREQ_DIFFERENCE;
123             String msg = String.format("Target %d: theoretical freq=%f; measured freq=%f; called %d times %s",
124                     i, theoreticalFreq, measuredFreq, callHistogram[i], freqIsOK ? " [OK]" : " [BAD, but acceptable: difference is too big]");
125 
126             // This test used to fail due to OS scheduler
127             // so it was refactored to just a stress test which doesn't fail if the frequency is wrong
128             if (!freqIsOK) {
129                 Env.complain(msg);
130             } else {
131                 Env.traceNormal(msg);
132             }
133         }
134 
135         return true;
136     }
137 
138     private static class Target {
139         // TODO: nanosleep?
140         private static final long[] DELAYS = new long[] { 1L, 3L, 5L, 7L, 9L, 11L };
141         public static final long TOTAL_DELAY;
142         static {
143             long total = 0L;
144             for (long i : DELAYS) {
145                 total += i;
146             }
147             TOTAL_DELAY = total;
148         }
149         public static Target[] createTargets(MethodHandles.Lookup lookup, INDIFY_RelinkCallSiteFreqTest test) {
150             Target[] result = new Target[DELAYS.length];
151             MethodHandle targetMH;
152             try {
153                 targetMH = lookup.findVirtual(INDIFY_RelinkCallSiteFreqTest.class, "target", MethodType.methodType(int.class, int.class));
154             } catch (NoSuchMethodException | IllegalAccessException e) {
155                 throw new Error(e);
156             }
157             for (int i = 0; i < result.length; ++i) {
158                 result[i] = new Target(DELAYS[i], MethodHandles.insertArguments(targetMH, 0, test, i));
159             }
160             return result;
161         }
162 
163         public final long delay;
164         public final MethodHandle mh;
165         public Target(long delay, MethodHandle mh) {
166             this.delay = delay;
167             this.mh = mh;
168         }
169         public void run(CallSite cs) throws InterruptedException {
170             cs.setTarget(mh);
171             Thread.sleep(delay);
172         }
173     }
174 
175     private static class FreqMeasurementThread extends Thread {
176         final long[] callHistogram;
177         long totalCalls = 0L;
178 
179         private final CyclicBarrier startBarrier;
180         private final INDIFY_RelinkCallSiteFreqTest test;
181 
182         public FreqMeasurementThread(CyclicBarrier startBarrier, INDIFY_RelinkCallSiteFreqTest test, int targetCount) {
183             setName(getClass().getSimpleName());
184             this.startBarrier = startBarrier;
185             this.test = test;
186             callHistogram = new long[targetCount];
187         }
188 
189         @Override
190         public void run() {
191             try {
192                 startBarrier.await();
193 
194                 while (!test.testDone) {
195                     int n = indyWrapper();
196                     ++callHistogram[n];
197                     ++totalCalls;
198                     Env.traceDebug("Called target: " + n);
199 
200                     Thread.yield();
201                 }
202             } catch (BrokenBarrierException e) {
203                 test.markTestFailed("TEST BUG: start barrier is not working", e);
204             } catch (Throwable t) {
205                 test.markTestFailed("Exception in thread " + getName(), t);
206             }
207         }
208     }
209 
210     // BSM + target
211 
212     private static MethodType MT_bootstrap() {
213         return MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, MethodType.class);
214     }
215 
216     private static MethodHandle MH_bootstrap() throws NoSuchMethodException, IllegalAccessException {
217         return MethodHandles.lookup().findStatic(INDIFY_RelinkCallSiteFreqTest.class, "bootstrap", MT_bootstrap());
218     }
219 
220     private static MethodHandle INDY_call;
221     private static MethodHandle INDY_call() throws Throwable {
222         if (INDY_call != null) {
223           return INDY_call;
224         }
225         CallSite cs = (CallSite) MH_bootstrap().invokeWithArguments(MethodHandles.lookup(), "gimmeTarget", MethodType.methodType(int.class));
226         return cs.dynamicInvoker();
227     }
228 
229     public static int indyWrapper() throws Throwable {
230         return (int) INDY_call().invokeExact();
231     }
232 
233     private static Object bootstrap(MethodHandles.Lookup l, String n, MethodType t) throws Throwable {
234         Env.traceVerbose("Bootstrap called");
235         return INDIFY_RelinkCallSiteFreqTest.cs;
236     }
237 
238     private int target(int n) {
239         return n;
240     }
241 
242     // End BSM + target
243 }
244