|
@@ -39,10 +39,12 @@ import java.lang.invoke.MutableCallSite;
|
|
|
* Based on the cascaded inlining cache from the JSR 292 cookbook
|
|
|
* (https://code.google.com/archive/p/jsr292-cookbook/, BSD license)
|
|
|
*/
|
|
|
-// NOTE: this class must be public, because generated painless classes are in a different package,
|
|
|
+// NOTE: this class must be public, because generated painless classes are in a different classloader,
|
|
|
// and it needs to be accessible by that code.
|
|
|
public final class DynamicCallSite {
|
|
|
|
|
|
+ private DynamicCallSite() {} // no instance!
|
|
|
+
|
|
|
// NOTE: these must be primitive types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
|
|
|
/** static bootstrap parameter indicating a dynamic method call, e.g. foo.bar(...) */
|
|
|
public static final int METHOD_CALL = 0;
|
|
@@ -55,20 +57,92 @@ public final class DynamicCallSite {
|
|
|
/** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
|
|
|
public static final int ARRAY_STORE = 4;
|
|
|
|
|
|
- static class InliningCacheCallSite extends MutableCallSite {
|
|
|
+ static final class InliningCacheCallSite extends MutableCallSite {
|
|
|
/** maximum number of types before we go megamorphic */
|
|
|
static final int MAX_DEPTH = 5;
|
|
|
|
|
|
- final Lookup lookup;
|
|
|
- final String name;
|
|
|
- final int flavor;
|
|
|
- int depth;
|
|
|
+ private final String name;
|
|
|
+ private final int flavor;
|
|
|
+ int depth; // pkg-protected for testing
|
|
|
|
|
|
- InliningCacheCallSite(Lookup lookup, String name, MethodType type, int flavor) {
|
|
|
+ InliningCacheCallSite(String name, MethodType type, int flavor) {
|
|
|
super(type);
|
|
|
- this.lookup = lookup;
|
|
|
this.name = name;
|
|
|
this.flavor = flavor;
|
|
|
+
|
|
|
+ MethodHandle fallback = FALLBACK.bindTo(this);
|
|
|
+ fallback = fallback.asCollector(Object[].class, type.parameterCount());
|
|
|
+ fallback = fallback.asType(type);
|
|
|
+
|
|
|
+ setTarget(fallback);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * guard method for inline caching: checks the receiver's class is the same
|
|
|
+ * as the cached class
|
|
|
+ */
|
|
|
+ static boolean checkClass(Class<?> clazz, Object receiver) {
|
|
|
+ return receiver.getClass() == clazz;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Does a slow lookup against the whitelist.
|
|
|
+ */
|
|
|
+ private static MethodHandle lookup(int flavor, Class<?> clazz, String name) {
|
|
|
+ switch(flavor) {
|
|
|
+ case METHOD_CALL:
|
|
|
+ return Def.lookupMethod(clazz, name, Definition.INSTANCE);
|
|
|
+ case LOAD:
|
|
|
+ return Def.lookupGetter(clazz, name, Definition.INSTANCE);
|
|
|
+ case STORE:
|
|
|
+ return Def.lookupSetter(clazz, name, Definition.INSTANCE);
|
|
|
+ case ARRAY_LOAD:
|
|
|
+ return Def.lookupArrayLoad(clazz);
|
|
|
+ case ARRAY_STORE:
|
|
|
+ return Def.lookupArrayStore(clazz);
|
|
|
+ default: throw new AssertionError();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
|
|
|
+ * types at this call site and given up on caching).
|
|
|
+ */
|
|
|
+ Object fallback(Object[] args) throws Throwable {
|
|
|
+ MethodType type = type();
|
|
|
+ Object receiver = args[0];
|
|
|
+ Class<?> receiverClass = receiver.getClass();
|
|
|
+ MethodHandle target = lookup(flavor, receiverClass, name);
|
|
|
+ target = target.asType(type);
|
|
|
+
|
|
|
+ if (depth >= MAX_DEPTH) {
|
|
|
+ // revert to a vtable call
|
|
|
+ setTarget(target);
|
|
|
+ return target.invokeWithArguments(args);
|
|
|
+ }
|
|
|
+
|
|
|
+ MethodHandle test = CHECK_CLASS.bindTo(receiverClass);
|
|
|
+ test = test.asType(test.type().changeParameterType(0, type.parameterType(0)));
|
|
|
+
|
|
|
+ MethodHandle guard = MethodHandles.guardWithTest(test, target, getTarget());
|
|
|
+ depth++;
|
|
|
+
|
|
|
+ setTarget(guard);
|
|
|
+ return target.invokeWithArguments(args);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final MethodHandle CHECK_CLASS;
|
|
|
+ private static final MethodHandle FALLBACK;
|
|
|
+ static {
|
|
|
+ final Lookup lookup = MethodHandles.lookup();
|
|
|
+ try {
|
|
|
+ CHECK_CLASS = lookup.findStatic(lookup.lookupClass(), "checkClass",
|
|
|
+ MethodType.methodType(boolean.class, Class.class, Object.class));
|
|
|
+ FALLBACK = lookup.findVirtual(lookup.lookupClass(), "fallback",
|
|
|
+ MethodType.methodType(Object.class, Object[].class));
|
|
|
+ } catch (ReflectiveOperationException e) {
|
|
|
+ throw new AssertionError(e);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -81,82 +155,7 @@ public final class DynamicCallSite {
|
|
|
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
|
|
|
*/
|
|
|
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int flavor) {
|
|
|
- InliningCacheCallSite callSite = new InliningCacheCallSite(lookup, name, type, flavor);
|
|
|
-
|
|
|
- MethodHandle fallback = FALLBACK.bindTo(callSite);
|
|
|
- fallback = fallback.asCollector(Object[].class, type.parameterCount());
|
|
|
- fallback = fallback.asType(type);
|
|
|
-
|
|
|
- callSite.setTarget(fallback);
|
|
|
- return callSite;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * guard method for inline caching: checks the receiver's class is the same
|
|
|
- * as the cached class
|
|
|
- */
|
|
|
- static boolean checkClass(Class<?> clazz, Object receiver) {
|
|
|
- return receiver.getClass() == clazz;
|
|
|
+ return new InliningCacheCallSite(name, type, flavor);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Does a slow lookup against the whitelist.
|
|
|
- */
|
|
|
- private static MethodHandle lookup(int flavor, Class<?> clazz, String name) {
|
|
|
- switch(flavor) {
|
|
|
- case METHOD_CALL:
|
|
|
- return Def.lookupMethod(clazz, name, Definition.INSTANCE);
|
|
|
- case LOAD:
|
|
|
- return Def.lookupGetter(clazz, name, Definition.INSTANCE);
|
|
|
- case STORE:
|
|
|
- return Def.lookupSetter(clazz, name, Definition.INSTANCE);
|
|
|
- case ARRAY_LOAD:
|
|
|
- return Def.lookupArrayLoad(clazz);
|
|
|
- case ARRAY_STORE:
|
|
|
- return Def.lookupArrayStore(clazz);
|
|
|
- default: throw new AssertionError();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
|
|
|
- * types at this call site and given up on caching).
|
|
|
- */
|
|
|
- static Object fallback(InliningCacheCallSite callSite, Object[] args) throws Throwable {
|
|
|
- MethodType type = callSite.type();
|
|
|
- Object receiver = args[0];
|
|
|
- Class<?> receiverClass = receiver.getClass();
|
|
|
- MethodHandle target = lookup(callSite.flavor, receiverClass, callSite.name);
|
|
|
- target = target.asType(type);
|
|
|
-
|
|
|
- if (callSite.depth >= InliningCacheCallSite.MAX_DEPTH) {
|
|
|
- // revert to a vtable call
|
|
|
- callSite.setTarget(target);
|
|
|
- return target.invokeWithArguments(args);
|
|
|
- }
|
|
|
-
|
|
|
- MethodHandle test = CHECK_CLASS.bindTo(receiverClass);
|
|
|
- test = test.asType(test.type().changeParameterType(0, type.parameterType(0)));
|
|
|
-
|
|
|
- MethodHandle guard = MethodHandles.guardWithTest(test, target, callSite.getTarget());
|
|
|
- callSite.depth++;
|
|
|
-
|
|
|
- callSite.setTarget(guard);
|
|
|
- return target.invokeWithArguments(args);
|
|
|
- }
|
|
|
-
|
|
|
- private static final MethodHandle CHECK_CLASS;
|
|
|
- private static final MethodHandle FALLBACK;
|
|
|
-
|
|
|
- static {
|
|
|
- Lookup lookup = MethodHandles.lookup();
|
|
|
- try {
|
|
|
- CHECK_CLASS = lookup.findStatic(DynamicCallSite.class, "checkClass",
|
|
|
- MethodType.methodType(boolean.class, Class.class, Object.class));
|
|
|
- FALLBACK = lookup.findStatic(DynamicCallSite.class, "fallback",
|
|
|
- MethodType.methodType(Object.class, InliningCacheCallSite.class, Object[].class));
|
|
|
- } catch (ReflectiveOperationException e) {
|
|
|
- throw new AssertionError(e);
|
|
|
- }
|
|
|
- }
|
|
|
}
|