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 }