Skip to content

Instantly share code, notes, and snippets.

@jruby
Created May 28, 2009 02:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jruby/119060 to your computer and use it in GitHub Desktop.
Save jruby/119060 to your computer and use it in GitHub Desktop.
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