8234401: ConstantCallSite may stuck in non-frozen state

Reviewed-by: psandoz
This commit is contained in:
Vladimir Ivanov 2019-11-26 16:09:17 +03:00
parent d5c759accb
commit e515a609e9
5 changed files with 94 additions and 24 deletions

View file

@ -87,9 +87,10 @@ private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String nam
abstract abstract
public class CallSite { public class CallSite {
// The actual payload of this call site: // The actual payload of this call site.
// Can be modified using {@link MethodHandleNatives#setCallSiteTargetNormal} or {@link MethodHandleNatives#setCallSiteTargetVolatile}.
/*package-private*/ /*package-private*/
MethodHandle target; // Note: This field is known to the JVM. Do not change. final MethodHandle target; // Note: This field is known to the JVM.
/** /**
* Make a blank call site object with the given method type. * Make a blank call site object with the given method type.
@ -129,11 +130,11 @@ public class CallSite {
*/ */
/*package-private*/ /*package-private*/
CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable { CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
this(targetType); this(targetType); // need to initialize target to make CallSite.type() work in createTargetHook
ConstantCallSite selfCCS = (ConstantCallSite) this; ConstantCallSite selfCCS = (ConstantCallSite) this;
MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS); MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS);
checkTargetChange(this.target, boundTarget); setTargetNormal(boundTarget); // ConstantCallSite doesn't publish CallSite.target
this.target = boundTarget; UNSAFE.storeStoreFence(); // barrier between target and isFrozen updates
} }
/** /**
@ -190,12 +191,13 @@ public class CallSite {
*/ */
public abstract void setTarget(MethodHandle newTarget); public abstract void setTarget(MethodHandle newTarget);
void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) { private void checkTargetChange(MethodHandle newTarget) {
MethodType oldType = oldTarget.type(); MethodType oldType = target.type(); // target is always present
MethodType newType = newTarget.type(); // null check! MethodType newType = newTarget.type(); // null check!
if (!newType.equals(oldType)) if (newType != oldType) {
throw wrongTargetType(newTarget, oldType); throw wrongTargetType(newTarget, oldType);
} }
}
private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) { private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) {
return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type); return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type);
@ -217,7 +219,7 @@ public class CallSite {
*/ */
public abstract MethodHandle dynamicInvoker(); public abstract MethodHandle dynamicInvoker();
/*non-public*/ /*package-private*/
MethodHandle makeDynamicInvoker() { MethodHandle makeDynamicInvoker() {
MethodHandle getTarget = getTargetHandle().bindArgumentL(0, this); MethodHandle getTarget = getTargetHandle().bindArgumentL(0, this);
MethodHandle invoker = MethodHandles.exactInvoker(this.type()); MethodHandle invoker = MethodHandles.exactInvoker(this.type());
@ -283,19 +285,24 @@ public class CallSite {
} }
/*package-private*/ /*package-private*/
void setTargetNormal(MethodHandle newTarget) { final void setTargetNormal(MethodHandle newTarget) {
checkTargetChange(newTarget);
MethodHandleNatives.setCallSiteTargetNormal(this, newTarget); MethodHandleNatives.setCallSiteTargetNormal(this, newTarget);
} }
/*package-private*/ /*package-private*/
MethodHandle getTargetVolatile() { final MethodHandle getTargetVolatile() {
return (MethodHandle) UNSAFE.getReferenceVolatile(this, getTargetOffset()); return (MethodHandle) UNSAFE.getReferenceVolatile(this, getTargetOffset());
} }
/*package-private*/ /*package-private*/
void setTargetVolatile(MethodHandle newTarget) { final void setTargetVolatile(MethodHandle newTarget) {
checkTargetChange(newTarget);
MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget); MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget);
} }
// this implements the upcall from the JVM, MethodHandleNatives.linkCallSite: // this implements the upcall from the JVM, MethodHandleNatives.linkCallSite:
/*package-private*/
static CallSite makeSite(MethodHandle bootstrapMethod, static CallSite makeSite(MethodHandle bootstrapMethod,
// Callee information: // Callee information:
String name, MethodType type, String name, MethodType type,

View file

@ -25,6 +25,9 @@
package java.lang.invoke; package java.lang.invoke;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.Stable;
/** /**
* A {@code ConstantCallSite} is a {@link CallSite} whose target is permanent, and can never be changed. * A {@code ConstantCallSite} is a {@link CallSite} whose target is permanent, and can never be changed.
* An {@code invokedynamic} instruction linked to a {@code ConstantCallSite} is permanently * An {@code invokedynamic} instruction linked to a {@code ConstantCallSite} is permanently
@ -33,7 +36,10 @@ package java.lang.invoke;
* @since 1.7 * @since 1.7
*/ */
public class ConstantCallSite extends CallSite { public class ConstantCallSite extends CallSite {
private final boolean isFrozen; private static final Unsafe UNSAFE = Unsafe.getUnsafe();
@Stable // should NOT be constant folded during instance initialization (isFrozen == false)
/*final*/ private boolean isFrozen;
/** /**
* Creates a call site with a permanent target. * Creates a call site with a permanent target.
@ -43,6 +49,7 @@ public class ConstantCallSite extends CallSite {
public ConstantCallSite(MethodHandle target) { public ConstantCallSite(MethodHandle target) {
super(target); super(target);
isFrozen = true; isFrozen = true;
UNSAFE.storeStoreFence(); // properly publish isFrozen update
} }
/** /**
@ -79,8 +86,9 @@ public class ConstantCallSite extends CallSite {
* @throws Throwable anything else thrown by the hook function * @throws Throwable anything else thrown by the hook function
*/ */
protected ConstantCallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable { protected ConstantCallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
super(targetType, createTargetHook); super(targetType, createTargetHook); // "this" instance leaks into createTargetHook
isFrozen = true; isFrozen = true;
UNSAFE.storeStoreFence(); // properly publish isFrozen
} }
/** /**

View file

@ -152,7 +152,6 @@ public class MutableCallSite extends CallSite {
* @see #getTarget * @see #getTarget
*/ */
@Override public void setTarget(MethodHandle newTarget) { @Override public void setTarget(MethodHandle newTarget) {
checkTargetChange(this.target, newTarget);
setTargetNormal(newTarget); setTargetNormal(newTarget);
} }

View file

@ -96,7 +96,6 @@ public class VolatileCallSite extends CallSite {
* @see #getTarget * @see #getTarget
*/ */
@Override public void setTarget(MethodHandle newTarget) { @Override public void setTarget(MethodHandle newTarget) {
checkTargetChange(getTargetVolatile(), newTarget);
setTargetVolatile(newTarget); setTargetVolatile(newTarget);
} }

View file

@ -24,10 +24,11 @@
/** /**
* @test * @test
* @summary smoke tests for CallSite * @summary smoke tests for CallSite
* @library /test/lib
* *
* @build indify.Indify * @build indify.Indify
* @compile CallSiteTest.java * @compile CallSiteTest.java
* @run main/othervm/timeout=3600 -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies * @run main/othervm/timeout=3600 -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies -Xbatch
* indify.Indify * indify.Indify
* --expand-properties --classpath ${test.classes} * --expand-properties --classpath ${test.classes}
* --java test.java.lang.invoke.CallSiteTest * --java test.java.lang.invoke.CallSiteTest
@ -40,6 +41,7 @@ import java.io.*;
import java.lang.invoke.*; import java.lang.invoke.*;
import static java.lang.invoke.MethodHandles.*; import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*; import static java.lang.invoke.MethodType.*;
import static jdk.test.lib.Asserts.*;
public class CallSiteTest { public class CallSiteTest {
private static final Class<?> CLASS = CallSiteTest.class; private static final Class<?> CLASS = CallSiteTest.class;
@ -51,16 +53,19 @@ public class CallSiteTest {
static { static {
try { try {
mh_foo = lookup().findStatic(CLASS, "foo", methodType(int.class, int.class, int.class)); mh_foo = lookup().findStatic(CLASS, "foo", methodType(int.class, int.class, int.class));
mh_bar = lookup().findStatic(CLASS, "bar", methodType(int.class, int.class, int.class)); mh_bar = lookup().findStatic(CLASS, "bar", methodType(int.class, int.class, int.class));
mcs = new MutableCallSite(mh_foo); mcs = new MutableCallSite(mh_foo);
vcs = new VolatileCallSite(mh_foo); vcs = new VolatileCallSite(mh_foo);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
throw new Error(e);
} }
} }
public static void main(String... av) throws Throwable { public static void main(String... av) throws Throwable {
testConstantCallSite();
testMutableCallSite(); testMutableCallSite();
testVolatileCallSite(); testVolatileCallSite();
} }
@ -69,9 +74,61 @@ public class CallSiteTest {
private static final int RESULT1 = 762786192; private static final int RESULT1 = 762786192;
private static final int RESULT2 = -21474836; private static final int RESULT2 = -21474836;
private static void assertEquals(int expected, int actual) { static final CallSite MCS = new MutableCallSite(methodType(void.class));
if (expected != actual) static final MethodHandle MCS_INVOKER = MCS.dynamicInvoker();
throw new AssertionError("expected: " + expected + ", actual: " + actual);
static void test(boolean shouldThrow) {
try {
MCS_INVOKER.invokeExact();
if (shouldThrow) {
throw new AssertionError("should throw");
}
} catch (IllegalStateException ise) {
if (!shouldThrow) {
throw new AssertionError("should not throw", ise);
}
} catch (Throwable e) {
throw new Error(e);
}
}
static class MyCCS extends ConstantCallSite {
public MyCCS(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
super(targetType, createTargetHook);
}
}
private static MethodHandle testConstantCallSiteHandler(CallSite cs, CallSite[] holder) throws Throwable {
holder[0] = cs; // capture call site instance for subsequent checks
MethodType csType = cs.type(); // should not throw on partially constructed instance
// Truly dynamic invoker for constant call site
MethodHandle getTarget = lookup().findVirtual(CallSite.class, "getTarget", MethodType.methodType(MethodHandle.class))
.bindTo(cs);
MethodHandle invoker = MethodHandles.exactInvoker(csType);
MethodHandle target = MethodHandles.foldArguments(invoker, getTarget);
MCS.setTarget(target);
// warmup
for (int i = 0; i < 20_000; i++) {
test(true); // should throw IllegalStateException
}
return MethodHandles.empty(csType); // initialize cs with an empty method handle
}
private static void testConstantCallSite() throws Throwable {
CallSite[] holder = new CallSite[1];
MethodHandle handler = lookup().findStatic(CLASS, "testConstantCallSiteHandler", MethodType.methodType(MethodHandle.class, CallSite.class, CallSite[].class));
handler = MethodHandles.insertArguments(handler, 1, new Object[] { holder } );
CallSite ccs = new MyCCS(MCS.type(), handler); // trigger call to handler
if (ccs != holder[0]) {
throw new AssertionError("different call site instances");
}
test(false); // should not throw
} }
private static void testMutableCallSite() throws Throwable { private static void testMutableCallSite() throws Throwable {
@ -83,11 +140,11 @@ public class CallSiteTest {
for (int n = 0; n < 2; n++) { for (int n = 0; n < 2; n++) {
mcs.setTarget(mh_foo); mcs.setTarget(mh_foo);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
assertEquals(RESULT1, runMutableCallSite()); assertEQ(RESULT1, runMutableCallSite());
} }
mcs.setTarget(mh_bar); mcs.setTarget(mh_bar);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
assertEquals(RESULT2, runMutableCallSite()); assertEQ(RESULT2, runMutableCallSite());
} }
} }
} }
@ -100,11 +157,11 @@ public class CallSiteTest {
for (int n = 0; n < 2; n++) { for (int n = 0; n < 2; n++) {
vcs.setTarget(mh_foo); vcs.setTarget(mh_foo);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
assertEquals(RESULT1, runVolatileCallSite()); assertEQ(RESULT1, runVolatileCallSite());
} }
vcs.setTarget(mh_bar); vcs.setTarget(mh_bar);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
assertEquals(RESULT2, runVolatileCallSite()); assertEQ(RESULT2, runVolatileCallSite());
} }
} }
} }