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