blob: 7c5c38a219766a4eab83af72575b5a6d156f1cbf [file] [log] [blame]
/*
* Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package vm.mlvm.indy.share;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import nsk.share.test.Stresser;
import vm.mlvm.share.Env;
import vm.mlvm.share.MlvmTest;
public abstract class INDIFY_RelinkCallSiteTest extends MlvmTest {
private static final int TARGET_COUNT = 1000000;
private static final int ARTIFICALLY_LOST_SYNC_PERIOD = 1000;
private static final CyclicBarrier startBarrier = new CyclicBarrier(2);
/**
* This class is used for synchronization between threads during relinking.
*
* We don't use volatile, synchronized(), or java.util.concurrent stuff
* to avoid artificial ordering across threads, except the one introduced by java.lang.invoke
* package itself.
*
* The variable is placed inside an array to make sure that it would occupy
* a separate CPU cache line than the CallSite.
*
* value < 0 and -value == previous-call-site-num: test in call site changing thread
* value > 0 and value == current-call-site-num: test in target (invokedynamic) calling thread
* value == 0: test is done, all threads must exit
*/
static class Sync {
int[] sync = new int[64];
int get() {
return this.sync[32];
}
void put (int v) {
this.sync[32] = v;
}
}
static Sync syncTargetNum = new Sync();
static CallSite cs;
static MethodHandle[] targets = new MethodHandle[TARGET_COUNT];
protected abstract CallSite createCallSite(MethodHandle mh);
@Override
public boolean run() throws Throwable {
// Create targets
MethodHandle targetMH = MethodHandles.lookup().findVirtual(INDIFY_RelinkCallSiteTest.class, "target", MethodType.methodType(int.class, int.class));
for (int i = 0; i < TARGET_COUNT; i++)
INDIFY_RelinkCallSiteTest.targets[i] = MethodHandles.insertArguments(targetMH, 0, this, i);
// Set current target number
INDIFY_RelinkCallSiteTest.syncTargetNum.put(1);
// Create call site
INDIFY_RelinkCallSiteTest.cs = createCallSite(INDIFY_RelinkCallSiteTest.targets[INDIFY_RelinkCallSiteTest.syncTargetNum.get()]);
// Call BSM
indyWrapper();
// Start call site altering thread
CallSiteAlteringThread csaThread = new CallSiteAlteringThread();
csaThread.setDaemon(true);
csaThread.start();
try {
// Start calling invokedynamic
Stresser stresser = createStresser();
stresser.start(1);
try {
int lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get();
int curTargetNum;
INDIFY_RelinkCallSiteTest.startBarrier.await();
while (stresser.continueExecution()) {
stresser.iteration();
curTargetNum = indyWrapper();
// This test used to fail due to OS scheduler
// so it was refactored to just a stress test which doesn't fail if the frequency is wrong
boolean artificallyLostSync = lastTargetNum % ARTIFICALLY_LOST_SYNC_PERIOD == 0;
if (lastTargetNum == curTargetNum) {
Env.traceDebug("Target " + curTargetNum + " called: OK");
if (artificallyLostSync) {
Env.complain("Test bug: invoked target (" + curTargetNum + ") should not match the one in CallSite (" + lastTargetNum + ")");
}
} else {
if (artificallyLostSync) {
// That's OK
} else {
Env.complain("Invoked target number (" + curTargetNum + ") does not match the one in CallSite (" + lastTargetNum + ")");
}
// OK, let's continue anyway
lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get();
}
// Synchronize without any "special" synchronization means
int syncCycles = 0;
INDIFY_RelinkCallSiteTest.syncTargetNum.put(-lastTargetNum);
while (INDIFY_RelinkCallSiteTest.syncTargetNum.get() < 0) {
Thread.yield();
curTargetNum = indyWrapper();
syncCycles++;
if (syncCycles % 100000 == 0) {
Env.traceDebug("Waiting for change: target " + curTargetNum + " called " + syncCycles + " times");
}
if (curTargetNum > lastTargetNum) {
Env.traceDebug("Target changed but not yet signalled to me: curTargetNum (" + curTargetNum + ") > lastTargetNum (" + lastTargetNum + ")");
} else if (curTargetNum < lastTargetNum && !artificallyLostSync) {
Env.complain("Synchronization lost again: curTargetNum (" + curTargetNum + ") < lastTargetNum (" + lastTargetNum + ")");
}
}
lastTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get();
if (lastTargetNum == 0) {
stresser.forceFinish();
}
}
} finally {
stresser.finish();
}
} finally {
INDIFY_RelinkCallSiteTest.syncTargetNum.put(0);
}
// Return false
return true;
// (Never trust comments :)
}
static class CallSiteAlteringThread extends Thread {
@Override
public void run() {
int curTargetNum = INDIFY_RelinkCallSiteTest.syncTargetNum.get();
try {
INDIFY_RelinkCallSiteTest.startBarrier.await();
} catch ( BrokenBarrierException e ) {
Env.complain(e, "Test bug: start barrier is not working");
} catch ( InterruptedException leave ) {
return;
}
while ( INDIFY_RelinkCallSiteTest.syncTargetNum.get() != 0 ) {
while ( INDIFY_RelinkCallSiteTest.syncTargetNum.get() != -curTargetNum )
Thread.yield();
curTargetNum++;
if ( curTargetNum >= TARGET_COUNT ) {
INDIFY_RelinkCallSiteTest.syncTargetNum.put(0);
break;
}
Env.traceDebug("Setting new target: " + curTargetNum);
// TODO: Check this and next line ordering in JMM
if ( curTargetNum % ARTIFICALLY_LOST_SYNC_PERIOD != 0 )
INDIFY_RelinkCallSiteTest.cs.setTarget(INDIFY_RelinkCallSiteTest.targets[curTargetNum]);
INDIFY_RelinkCallSiteTest.syncTargetNum.put(curTargetNum);
}
}
}
// BSM + target
private static MethodType MT_bootstrap() {
return MethodType.methodType(Object.class, Object.class, Object.class, Object.class);
}
private static MethodHandle MH_bootstrap() throws NoSuchMethodException, IllegalAccessException {
return MethodHandles.lookup().findStatic(INDIFY_RelinkCallSiteTest.class, "bootstrap", MT_bootstrap());
}
private static MethodHandle INDY_call;
private static MethodHandle INDY_call() throws Throwable {
if (INDY_call != null) {
return INDY_call;
}
CallSite cs = (CallSite) MH_bootstrap ().invokeWithArguments(MethodHandles.lookup(), "gimmeTarget", MethodType.methodType(int.class));
return cs.dynamicInvoker();
}
public static int indyWrapper() throws Throwable {
return (int) INDY_call().invokeExact();
}
private static Object bootstrap (Object l, Object n, Object t) throws Throwable {
Env.traceVerbose("Bootstrap called");
return INDIFY_RelinkCallSiteTest.cs;
}
private int target(int n) {
return n;
}
// End BSM + target
}