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 37 public abstract class INDIFY_RelinkCallSiteTest extends MlvmTest { 38 39 private static final int TARGET_COUNT = 1000000; 40 private static final int ARTIFICALLY_LOST_SYNC_PERIOD = 1000; 41 private static final CyclicBarrier startBarrier = new CyclicBarrier(2); 42 43 /** 44 * This class is used for synchronization between threads during relinking. 45 * 46 * We don't use volatile, synchronized(), or java.util.concurrent stuff 47 * to avoid artificial ordering across threads, except the one introduced by java.lang.invoke 48 * package itself. 49 * 50 * The variable is placed inside an array to make sure that it would occupy 51 * a separate CPU cache line than the CallSite. 52 * 53 * value < 0 and -value == previous-call-site-num: test in call site changing thread 54 * value > 0 and value == current-call-site-num: test in target (invokedynamic) calling thread 55 * value == 0: test is done, all threads must exit 56 */ 57 static class Sync { 58 int[] sync = new int[64]; 59 60 int get() { 61 return this.sync[32]; 62 } 63 64 void put (int v) { 65 this.sync[32] = v; 66 } 67 } 68 69 static Sync syncTargetNum = new Sync(); 70 static CallSite cs; 71 static MethodHandle[] targets = new MethodHandle[TARGET_COUNT]; 72 73 protected abstract CallSite createCallSite(MethodHandle mh); 74 75 @Override 76 public boolean run() throws Throwable { 77 // Create targets 78 MethodHandle targetMH = MethodHandles.lookup().findVirtual(INDIFY_RelinkCallSiteTest.class, "target", MethodType.methodType(int.class, int.class)); 79 for (int i = 0; i < TARGET_COUNT; i++) 80 INDIFY_RelinkCallSiteTest.targets[i] = MethodHandles.insertArguments(targetMH, 0, this, i); 81 82 // Set current target number 83 INDIFY_RelinkCallSiteTest.syncTargetNum.put(1); 84 85 // Create call site 86 INDIFY_RelinkCallSiteTest.cs = createCallSite(INDIFY_RelinkCallSiteTest.targets[INDIFY_RelinkCallSiteTest.syncTargetNum.get()]); 87 88 // Call BSM 89 indyWrapper(); 90 91 // Start call site altering thread 92 CallSiteAlteringThread csaThread = new CallSiteAlteringThread(); 93 csaThread.setDaemon(true); 94 csaThread.start(); 95 96 try { 97 // Start calling invokedynamic 98 Stresser stresser = createStresser(); 99 stresser.start(1); 100 try { 101 int lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get(); 102 int curTargetNum; 103 104 INDIFY_RelinkCallSiteTest.startBarrier.await(); 105 106 while (stresser.continueExecution()) { 107 stresser.iteration(); 108 109 curTargetNum = indyWrapper(); 110 111 // This test used to fail due to OS scheduler 112 // so it was refactored to just a stress test which doesn't fail if the frequency is wrong 113 boolean artificallyLostSync = lastTargetNum % ARTIFICALLY_LOST_SYNC_PERIOD == 0; 114 if (lastTargetNum == curTargetNum) { 115 Env.traceDebug("Target " + curTargetNum + " called: OK"); 116 if (artificallyLostSync) { 117 Env.complain("Test bug: invoked target (" + curTargetNum + ") should not match the one in CallSite (" + lastTargetNum + ")"); 118 } 119 } else { 120 if (artificallyLostSync) { 121 // That's OK 122 } else { 123 Env.complain("Invoked target number (" + curTargetNum + ") does not match the one in CallSite (" + lastTargetNum + ")"); 124 } 125 126 // OK, let's continue anyway 127 lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get(); 128 } 129 130 // Synchronize without any "special" synchronization means 131 int syncCycles = 0; 132 INDIFY_RelinkCallSiteTest.syncTargetNum.put(-lastTargetNum); 133 while (INDIFY_RelinkCallSiteTest.syncTargetNum.get() < 0) { 134 Thread.yield(); 135 curTargetNum = indyWrapper(); 136 syncCycles++; 137 138 if (syncCycles % 100000 == 0) { 139 Env.traceDebug("Waiting for change: target " + curTargetNum + " called " + syncCycles + " times"); 140 } 141 142 if (curTargetNum > lastTargetNum) { 143 Env.traceDebug("Target changed but not yet signalled to me: curTargetNum (" + curTargetNum + ") > lastTargetNum (" + lastTargetNum + ")"); 144 } else if (curTargetNum < lastTargetNum && !artificallyLostSync) { 145 Env.complain("Synchronization lost again: curTargetNum (" + curTargetNum + ") < lastTargetNum (" + lastTargetNum + ")"); 146 } 147 } 148 149 lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get(); 150 if (lastTargetNum == 0) { 151 stresser.forceFinish(); 152 } 153 } 154 155 } finally { 156 stresser.finish(); 157 } 158 } finally { 159 INDIFY_RelinkCallSiteTest.syncTargetNum.put(0); 160 } 161 162 // Return false 163 return true; 164 // (Never trust comments :) 165 } 166 167 static class CallSiteAlteringThread extends Thread { 168 @Override 169 public void run() { 170 int curTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get(); 171 172 try { 173 INDIFY_RelinkCallSiteTest.startBarrier.await(); 174 } catch ( BrokenBarrierException e ) { 175 Env.complain(e, "Test bug: start barrier is not working"); 176 } catch ( InterruptedException leave ) { 177 return; 178 } 179 180 while ( INDIFY_RelinkCallSiteTest.syncTargetNum.get() != 0 ) { 181 while ( INDIFY_RelinkCallSiteTest.syncTargetNum.get() != -curTargetNum ) 182 Thread.yield(); 183 184 curTargetNum++; 185 if ( curTargetNum >= TARGET_COUNT ) { 186 INDIFY_RelinkCallSiteTest.syncTargetNum.put(0); 187 break; 188 } 189 190 Env.traceDebug("Setting new target: " + curTargetNum); 191 // TODO: Check this and next line ordering in JMM 192 if ( curTargetNum % ARTIFICALLY_LOST_SYNC_PERIOD != 0 ) 193 INDIFY_RelinkCallSiteTest.cs.setTarget(INDIFY_RelinkCallSiteTest.targets[curTargetNum]); 194 INDIFY_RelinkCallSiteTest.syncTargetNum.put(curTargetNum); 195 } 196 } 197 } 198 199 // BSM + target 200 201 private static MethodType MT_bootstrap() { 202 return MethodType.methodType(Object.class, Object.class, Object.class, Object.class); 203 } 204 205 private static MethodHandle MH_bootstrap() throws NoSuchMethodException, IllegalAccessException { 206 return MethodHandles.lookup().findStatic(INDIFY_RelinkCallSiteTest.class, "bootstrap", MT_bootstrap()); 207 } 208 209 private static MethodHandle INDY_call; 210 private static MethodHandle INDY_call() throws Throwable { 211 if (INDY_call != null) { 212 return INDY_call; 213 } 214 CallSite cs = (CallSite) MH_bootstrap ().invokeWithArguments(MethodHandles.lookup(), "gimmeTarget", MethodType.methodType(int.class)); 215 return cs.dynamicInvoker(); 216 } 217 218 public static int indyWrapper() throws Throwable { 219 return (int) INDY_call().invokeExact(); 220 } 221 222 private static Object bootstrap (Object l, Object n, Object t) throws Throwable { 223 Env.traceVerbose("Bootstrap called"); 224 return INDIFY_RelinkCallSiteTest.cs; 225 } 226 227 private int target(int n) { 228 return n; 229 } 230 231 // End BSM + target 232 }