The invokedynamic work from the
Da Vinci Machine Project has come a long way. In fact, it has started to make it's way into OpenJDK 7 builds. To try the bleeding edge version out for yourself, follow these
instructions to patch and build your own. But, be warned, it's a bit tricky to get started. You may find that these examples will work with
OpenJDK 7 binaries (sooner or later they should!). For those who haven't heard about this work yet, invokedynamic, which is a part of
JSR-292, allows a program to dynamically call methods with (potentially) the same performance characteristics as static calls. Here is about the simplest invokedynamic program you can write:
import java.dyn.CallSite;
import java.dyn.InvokeDynamic;
import java.dyn.Linkage;
import java.dyn.MethodType;
import java.dyn.MethodHandles;
public class Example {
public static void main(String... av) throws Throwable {
for (String a : av) {
InvokeDynamic.call(a);
}
InvokeDynamic.call2(av[0]);
}
public static void foo(String x) {
System.out.println("Hello, " + x);
}
static { Linkage.registerBootstrapMethod("linkDynamic"); }
private static CallSite linkDynamic(Class caller, String name, MethodType type) {
System.out.println("linking:" + name);
CallSite c = new CallSite(caller, name, type);
c.setTarget(MethodHandles.lookup().findStatic(Example1.class, "foo",
MethodType.make(void.class, String.class)));
return c;
}
}
First notice the linkDynamic method. The static initializer above it registers that method as a special bootstrap method that invokedynamic will use when you want to call a method dynamically. The interesting bit to notice are the two calls to methods on InvokeDynamic. InvokeDynamic has no actual methods of its own. You can call any method on it (here I use "call" and "call2", but it could just as well have been "foo123" or "sasquatch") When you make these calls, control is passed to the bootstrap method (linkDynamic in this case). linkDynamic gets passed the Class of the caller, the name that was used ("call" and "call2" in this program) and the MethodType of the call, which specifieds the signature of the dynamic call.
To compile the above code, execute:
javac -XDinvokedynamic Example.java
To run it:
java -XX:+EnableMethodHandles -XX:+EnableInvokeDynamic Example a b c
Which should produce:
linking:call
Hello, a
Hello, b
Hello, c
linking:call2
Hello, a
Notice that we called "call" three times, but it only needed to be linked once. We then called call2 and it was separately linked. Had we invoked call2 again it would not have needed linking. Next in my exploration, I'll wire it up with some Python internal dispatch logic:
package org.python.compiler;
import java.dyn.CallSite;
import java.dyn.InvokeDynamic;
import java.dyn.Linkage;
import java.dyn.MethodType;
import java.dyn.MethodHandle;
import java.dyn.MethodHandles;
import java.dyn.MethodType;
import org.python.core.Py;
import org.python.core.PyCode;
import org.python.core.PyFunction;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyTuple;
import org.python.core.ThreadState;
public class IndyTest {
public static void run() throws Throwable {
PyObject result = InvokeDynamic.foo(new PyInteger(4), Py.None,
Py.EmptyObjects, new PyTuple());
System.out.println("result = " + result);
}
static { Linkage.registerBootstrapMethod("linkDynamic"); }
private static CallSite linkDynamic(Class caller, String name, MethodType type) {
System.out.println("linkDynamic...");
CallSite site = new CallSite(caller, name, type);
ThreadState ts = Py.getThreadState();
PyCode func_code = ((PyFunction)ts.frame.getname(name)).func_code;
MethodType oneArgCall = MethodType.make(
PyObject.class, //return type
ThreadState.class, // state
PyObject.class, // arg1
PyObject.class, // globals
PyObject[].class, // defaults
PyObject.class // closure
);
MethodHandle call = MethodHandles.lookup().findVirtual(PyCode.class, "call", oneArgCall);
call = MethodHandles.insertArguments(call, 0, func_code, ts);
call = MethodHandles.convertArguments(call, type);
site.setTarget(call);
return site;
}
}
There is a bit more going on here. In short, we look up the current state of the Jython interpreter when this is invoked and look for a function called "foo". We pass the PyInteger 4 (plus some boilerplate at the end -- in the future we should be able to only pass the 4, but MethodHandles.insertArguments can only add three arguments at the moment.) to the foo(), and then print the result out. Again "foo" is bound the first time it is called. When we create a CallSite, grab the current ThreadState and get our "foo" object from the current Jython frame. We then create a MethodType that corresponds to the way that we call Jython methods internally, and use methods off of MethodHandles (insertArguments and convertArguments) to coerce our call to the internal call semantics. Then our "foo" call is permanently bound to the function we found. Ultimately this is not what we will want since "foo" could be rebound in Jython in a number of ways. We'll get to this problem in a future post. To try this code out, we need a Jython program with a foo method:
def foo(x):
print "hello %s!" % x
foo("test")
from org.python.compiler import IndyTest
IndyTest.run()
def foo(x):
print "goodbye %s!" % x
foo("test")
IndyTest.run()
Run it like this (requires a Jython 2.5 install and we need to call Jython with --boot to put the Jython internals into the boot classpath):
jython --boot -J-XX:+EnableMethodHandles -J-XX:+EnableInvokeDynamic indy.py
hello test!
foo = 1
linkDynamic...
hello 4!
result = None
goodbye test!
foo = 2
hello 4!
result = None
The first IndyTest.run() looks great (hello 4!) -- but the second call still emits "hello 4!" even though foo() has been redefined. We'll look at fixing this in the next post.
By the way, to *really* learn about the guts of invokedynamic look to the posts from
John Rose and
Fredrik Öhrström along with the
Da Vinci Machine site.
View comments