Too much deopt
Rémi Forax
forax at univ-mlv.fr
Wed Oct 20 07:10:25 PDT 2010
I've written a small program in order in the hope to implement a simple
invalidation mechanism
using a global volatile field.
The test case changes the implementation of String.toUpperCase in the
main thread
and the spawned thread must see the change i.e the call site must be
updated accordingly.
This program crash the server VM of the latest binary build (b114).
It doesn't crash with server VM from the davinci workspace but
the VM does too much deopt.
I remember that John try to fix a similar bug just before the JVM Summit but
it wasn't a complete fix.
output of the VM with -XX:+PrintCompilation
1% made not entrant (2)
jsr292.cookbook.InvalidationExample$TestRunnable::run @ -2 (45 bytes)
2% ! jsr292.cookbook.InvalidationExample$TestRunnable::run @ 0 (45
bytes)
2% made not entrant (2)
jsr292.cookbook.InvalidationExample$TestRunnable::run @ -2 (45 bytes)
3% ! jsr292.cookbook.InvalidationExample$TestRunnable::run @ 0 (45
bytes)
3% made not entrant (2)
jsr292.cookbook.InvalidationExample$TestRunnable::run @ -2 (45 bytes)
4% ! jsr292.cookbook.InvalidationExample$TestRunnable::run @ 0 (45
bytes)
4% made not entrant (2)
jsr292.cookbook.InvalidationExample$TestRunnable::run @ -2 (45 bytes)
5% ! jsr292.cookbook.InvalidationExample$TestRunnable::run @ 0 (45
bytes)
5% made not entrant (2)
jsr292.cookbook.InvalidationExample$TestRunnable::run @ -2 (45 bytes)
6% ! jsr292.cookbook.InvalidationExample$TestRunnable::run @ 0 (45
bytes)
Rémi
--------------------------------------------------------------------------------------------------------
package jsr292.cookbook;
import java.dyn.CallSite;
import java.dyn.InvokeDynamic;
import java.dyn.Linkage;
import java.dyn.MethodHandle;
import java.dyn.MethodHandles;
import java.dyn.MethodHandles.Lookup;
import java.dyn.MethodType;
import java.dyn.NoAccessException;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class InvalidationExample {
public static String myToUpperCase(String s) {
return "foobar";
}
static class TestRunnable implements Runnable {
private final Object o ="foo";
@Override
public void run() {
try {
for(;;) {
if (InvokeDynamic.<String>toUpperCase(o)=="foobar")
break;
}
} catch(Throwable t) {
throw (AssertionError)new AssertionError().initCause(t);
}
System.out.println("thread exit");
}
static {
Linkage.registerBootstrapMethod(InvalidationExample.class,
"bootstrap");
}
}
public static void main(String[] args) throws Throwable {
Thread t = new Thread(new TestRunnable());
t.start();
Thread.sleep(1000);
MethodHandle myToUpperCase = MethodHandles.publicLookup().
findStatic(InvalidationExample.class, "myToUpperCase",
MethodType.methodType(String.class, String.class));
MetaClass.getMetaClass(String.class).insertMethod("toUpperCase",
myToUpperCase);
t.join();
System.out.println("done");
}
public static class MetaClass {
private final Class<?> currentClass;
private final ConcurrentHashMap<String, MethodHandle> methods =
new ConcurrentHashMap<String, MethodHandle>();
private MetaClass(Class<?> clazz) {
this.currentClass = clazz;
}
MethodHandle lookupMethod(String methodName, MethodType methodType) {
MethodHandle mh = methods.get(methodName);
if (mh != null)
return mh;
// oversimplified lookup, signature of the callsite and the one of
// the target method must match exactly
try {
mh = MethodHandles.publicLookup().findVirtual(currentClass,
methodName, methodType);
} catch(NoAccessException e) {
throw (LinkageError)new LinkageError(e.getMessage()).initCause(e);
}
methods.put(methodName, mh); // cache too much ??
return mh;
}
public void insertMethod(String methodName, MethodHandle mh) {
methods.put(methodName, mh);
timeStamp.getAndIncrement(); // volatile write
}
// must be initialized with something greater than zero because
// StructuralCallSite.verifiedTargetEpoch is initialized with zero
private static final AtomicInteger timeStamp =
new AtomicInteger(1);
private static final WeakHashMap<Class<?>, MetaClass> metaClassMap =
new WeakHashMap<Class<?>, InvalidationExample.MetaClass>();
public static int getTimeStamp() {
return timeStamp.get(); // volatile read
}
public static MetaClass getMetaClass(Class<?> clazz) {
MetaClass metaClass;
synchronized (metaClassMap) {
metaClass = metaClassMap.get(clazz);
if (metaClass == null) {
metaClass = new MetaClass(clazz);
metaClassMap.put(clazz, metaClass);
}
}
return metaClass;
}
}
public static CallSite bootstrap(Class<?> declaringClass, String
name, MethodType type) {
return new StructuralCallSite(name, type);
}
public static class StructuralCallSite extends CallSite {
private final MethodHandle fallback;
private MethodHandle verifiedTarget;
private int verifiedTargetEpoch;
StructuralCallSite(String name, MethodType type) {
MethodHandle mh = MethodHandles.insertArguments(FALLBACK, 0,
this, name);
mh = MethodHandles.collectArguments(mh, type);
this.fallback = mh;
setTarget(mh);
}
public Object fallback(String name, Object[] args) throws Throwable {
MethodType type = getTarget().type();
Class<?> receiverClass = args[0].getClass();
MetaClass metaclass = MetaClass.getMetaClass(receiverClass);
// if the timestamp change between when the timestamp is get and
// when the method is looked up, this is not a problem.
// the method will be invalidated later when the guard will fail
int epoch = MetaClass.getTimeStamp(); // volatile read
MethodHandle mh = metaclass.lookupMethod(name,
type.dropParameterTypes(0, 1));
mh = mh.asType(type);
// We need to be sure that verifiedTarget and verifiedEpoch
// are up to date, so we use a synchronized block
// we use fallback as a monitor object just because
// it's a private final object
MethodHandle target;
synchronized(fallback) {
// Discard the previously computed branch if epoch doesn't match
// we can't reuse the result of getTarget() here because a target
// of a structural callsite always starts with a timestamp guard
if (this.verifiedTargetEpoch == epoch) {
target = verifiedTarget;
} else {
target = fallback;
this.verifiedTargetEpoch = epoch;
}
MethodHandle receiverTest =
MethodHandles.insertArguments(RECEIVER_TEST, 1, receiverClass);
receiverTest =
receiverTest.asType(MethodType.methodType(boolean.class,
type.parameterType(0)));
target = MethodHandles.guardWithTest(receiverTest, mh, target);
verifiedTarget = target;
}
// another thread may see an old callsite target, it's not a
problem because
// all target are protected with a guard
MethodHandle timeStampTest =
MethodHandles.insertArguments(TIMESTAMP_TEST, 0, epoch);
MethodHandle unverifiedTarget =
MethodHandles.guardWithTest(timeStampTest, target, fallback);
setTarget(unverifiedTarget);
return target.invokeVarargs(args);
}
public static boolean timeStampTest(int epoch) {
return MetaClass.getTimeStamp() == epoch; // volatile read
}
public static boolean receiverTest(Object receiver, Class<?> clazz) {
return receiver.getClass() == clazz;
}
private static final MethodHandle FALLBACK;
private static final MethodHandle TIMESTAMP_TEST;
private static final MethodHandle RECEIVER_TEST;
static {
Lookup lookup = MethodHandles.lookup();
FALLBACK = lookup.findVirtual(StructuralCallSite.class, "fallback",
MethodType.methodType(Object.class, String.class,
Object[].class));
TIMESTAMP_TEST = lookup.findStatic(StructuralCallSite.class,
"timeStampTest",
MethodType.methodType(boolean.class, int.class));
RECEIVER_TEST = lookup.findStatic(StructuralCallSite.class,
"receiverTest",
MethodType.methodType(boolean.class, Object.class, Class.class));
}
}
}
More information about the mlvm-dev
mailing list