Created
May 28, 2009 02:55
-
-
Save jruby/119060 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class InvokeDynamicSupport { | |
// Subclass of CallSite to hold the "call type" for Ruby purposes | |
public static class JRubyCallSite extends CallSite { | |
private final CallType callType; | |
public JRubyCallSite(Class caller, String name, MethodType type, CallType callType) { | |
super(caller, name, type); | |
this.callType = callType; | |
} | |
public CallType callType() { | |
return callType; | |
} | |
} | |
// Bootstrap creates a call site and installs the basic fallback to start | |
public static CallSite bootstrap(Class caller, String name, MethodType type) { | |
JRubyCallSite site; | |
if (name == "call") { | |
site = new JRubyCallSite(caller, name, type, CallType.NORMAL); | |
} else { | |
site = new JRubyCallSite(caller, name, type, CallType.FUNCTIONAL); | |
} | |
// FIXME: hardcoded to 0-arg version | |
// Poller checks for thread events and retrieves the "self class" | |
MethodHandle poller = adapt() | |
.drop(0, ThreadContext.class, IRubyObject.class) | |
.drop(1, String.class) | |
.go(POLL_GET_CLASS); | |
// For fallback we insert the call site and combine in the results of poll+getClass in poller | |
MethodType fallbackType = type.insertParameterType(0, JRubyCallSite.class); | |
MethodHandle myFallback = adapt() | |
.insert(0, site) | |
.combine(4, poller) | |
.go(MethodHandles.lookup().findStatic(InvokeDynamicSupport.class, "fallback", fallbackType)); | |
site.setTarget(myFallback); | |
return site; | |
} | |
// The one, simple test method for all guardWithTest here | |
public static boolean test(CacheEntry entry, RubyClass selfClass) { | |
return entry.typeOk(selfClass); | |
} | |
// The zero-arg path for fallback; note the createGWT call which | |
// passes the simple test method, this fallback method, and a direct | |
// handle to the DynamicMethod's zero-arg 'call' method | |
public static IRubyObject fallback(JRubyCallSite site, ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass selfClass, String name) { | |
CacheEntry entry = selfClass.searchWithCache(name); | |
if (methodMissing(entry, site.callType(), name, caller)) { | |
return callMethodMissing(entry, site.callType(), context, self, name); | |
} | |
site.setTarget(createGWT(TEST, DYNMETH_0, FALLBACK_0, entry, site)); | |
return entry.method.call(context, self, selfClass, name); | |
} | |
// create a guardWithTest by assembling appropriate MethodHandles to wrap | |
// the simple test, the DynamicMethod.call, and the fallback version. | |
// Note that there's no hand-written Java code in the fast path anymore. | |
private static MethodHandle createGWT(MethodHandle test, MethodHandle target, MethodHandle fallback, CacheEntry entry, CallSite site, Class... nameAndArgs) { | |
// incoming: ThreadContext, IRubyObject, IRubyObject, RubyClass, String | |
MethodHandle myTest = adapt() | |
.drop(0, PRE_CLASS_ARGS) // drop ThreadContext, IRubyObject, IRubyObject | |
.drop(1, nameAndArgs) // drop String | |
.insert(0, entry) // insert CacheEntry | |
.go(test); // adapted to boolean test(CacheEntry, RubyClass) | |
// incoming: ThreadContext, IRubyObject, IRubyObject, RubyClass, String | |
MethodHandle getMethod = adapt() | |
.drop(0, PRE_CLASS_ARGS) // drop ThreadContext, IRubyObject, IRubyObject | |
.drop(0, RubyClass.class) // drop RubyClass | |
.drop(0, nameAndArgs) // drop String | |
.insert(0, entry) // insert CacheEntry | |
.go(METHOD_GETTER); // adapted to field access entry.method | |
// incoming: ThreadContext, IRubyObject, IRubyObject, RubyClass, String | |
MethodHandle myTarget = adapt() | |
.drop(1, IRubyObject.class) // drop first IRubyObject | |
.combine(0, getMethod) // insert method from getMethod at 0 | |
.go(target); // adapted to method.call(ThreadContext, IRubyObject, RubyClass, String) | |
// incoming: ThreadContext, IRubyObject, IRubyObject, RubyClass, String | |
MethodHandle myFallback = adapt() | |
.insert(0, site) // insert JRubyCallSite | |
.go(fallback); // adapted to IRubyObject fallback(JRubyCallSite, ThreadContext, IRubyObject, IRubyObject, RubyClass, String) | |
// incoming: ThreadContext, IRubyObject, IRubyObject, String | |
MethodHandle poller = adapt() | |
.drop(1, IRubyObject.class) // drop TC and first IRubyObject | |
.drop(2, nameAndArgs) // drop String | |
.go(POLL_GET_CLASS); // adapted to RubyClass pollAndGetClass(IRubyObject.class | |
// incoming: ThreadContext, IRubyObject, IRubyObject, String | |
MethodHandle guardWithTest = adapt() | |
.combine(3, poller) // combine in RubyClass from poller | |
.guard(myTest, myTarget, myFallback); // adapted for ThreadContext, IRubyObject, IRubyObject, RubyClass, String | |
return guardWithTest; | |
} | |
////// END of indy/MH logic ////// | |
// This is the start of a small adapter DSL to make composing handles easier | |
// Adapt starts the adaptation process with a dummy "start" adapter | |
private static Adaptation adapt() { | |
return new Start(); | |
} | |
// Adaptation is the class; it forms a linked list of "adaptations" as it goes | |
private static abstract class Adaptation { | |
private Adaptation previous; | |
public Adaptation(Adaptation previous) { | |
this.previous = previous; | |
} | |
public Adaptation previous() { | |
return previous; | |
} | |
// go is the final method called on an adaptation chain, playing | |
// all adapters in reverse against the target handle, producing a | |
// new incoming handle | |
public abstract MethodHandle go(MethodHandle handle); | |
// dropArguments | |
public Adaptation drop(int index, Class... types) { | |
return new Drop(this, index, types); | |
} | |
// insertArgument | |
public Adaptation insert(int index, Object value) { | |
return new Insert(this, index, value); | |
} | |
// combineArguments | |
public Adaptation combine(int index, MethodHandle combiner) { | |
return new Combine(this, index, combiner); | |
} | |
// Terminate the adapter at a guardWithTest | |
public MethodHandle guard(MethodHandle test, MethodHandle target, MethodHandle fallback) { | |
return go(MethodHandles.guardWithTest(test, target, fallback)); | |
} | |
} | |
private static class Start extends Adaptation { | |
public Start() { | |
super(null); | |
} | |
public MethodHandle go(MethodHandle handle) { | |
return handle; | |
} | |
} | |
private static class Drop extends Adaptation { | |
private int index; | |
private Class[] types; | |
public Drop(Adaptation previous, int index, Class... types) { | |
super(previous); | |
this.index = index; | |
this.types = types; | |
} | |
public MethodHandle go(MethodHandle handle) { | |
return previous().go(MethodHandles.dropArguments(handle, index, types)); | |
} | |
} | |
private static class Insert extends Adaptation { | |
private int index; | |
private Object value; | |
public Insert(Adaptation previous, int index, Object value) { | |
super(previous); | |
this.index = index; | |
this.value = value; | |
} | |
public MethodHandle go(MethodHandle handle) { | |
return MethodHandles.insertArgument(handle, index, value); | |
} | |
} | |
private static class Combine extends Adaptation { | |
private int index; | |
private MethodHandle combiner; | |
public Combine(Adaptation previous, int index, MethodHandle combiner) { | |
super(previous); | |
this.index = index; | |
this.combiner = combiner; | |
} | |
public MethodHandle go(MethodHandle handle) { | |
return MethodHandles.combineArguments(handle, index, combiner); | |
} | |
} | |
// some utility methods | |
private static MethodHandle lookupStatic(Class cls, String name, MethodType type) { | |
return MethodHandles.lookup().findStatic(cls, name, type); | |
} | |
private static MethodHandle lookupVirtual(Class cls, String name, MethodType type) { | |
return MethodHandles.lookup().findVirtual(cls, name, type); | |
} | |
private static MethodHandle lookupVirtual(Class cls, String name, Class retval, Class... args) { | |
return MethodHandles.lookup().findVirtual(cls, name, MethodType.make(retval, args)); | |
} | |
// some pre-allocated method types and handles | |
private static final MethodType SIMPLE_TEST = MethodType.make(boolean.class, CacheEntry.class, RubyClass.class); | |
private static final Class[] PRE_CLASS_ARGS = new Class[] {ThreadContext.class, IRubyObject.class, IRubyObject.class}; | |
private static final MethodHandle TEST = lookupStatic(InvokeDynamicSupport.class, "test", SIMPLE_TEST); | |
private static final MethodHandle DYNMETH_0 = lookupVirtual(DynamicMethod.class, "call", | |
IRubyObject.class, ThreadContext.class, IRubyObject.class, RubyClass.class, String.class); | |
private static final MethodHandle FALLBACK_0 = MethodHandles.lookup().findStatic(InvokeDynamicSupport.class, "fallback", | |
MethodType.make(IRubyObject.class, JRubyCallSite.class, ThreadContext.class, IRubyObject.class, IRubyObject.class, RubyClass.class, String.class)); | |
private static final MethodHandle POLL_GET_CLASS; | |
private static final MethodHandle METHOD_GETTER; | |
static { | |
MethodHandle mg = null; | |
try { | |
mg = MethodHandles.lookup().unreflectGetter(CacheEntry.class.getField("method")); | |
} catch (NoSuchFieldException nsfe) { | |
} | |
METHOD_GETTER = mg; | |
// prepare pollGetClass | |
MethodHandle pgc = adapt() | |
.drop(1, IRubyObject.class) | |
.drop(2, String.class) | |
.go(lookupStatic(InvokeDynamicSupport.class, "pollAndGetClass", MethodType.make(RubyClass.class, ThreadContext.class, IRubyObject.class))); | |
POLL_GET_CLASS = pgc; | |
} | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment