There are 4 JVM bytecodes to call methods:
invokeinterface - used to call an interface method on an object
invokestatic - used to call a static method of a class
invokevirtual - used to call a overridable method
invokespecial - used to call
Please refer to the JVM specification for further details.
To facilitate the compilation of dynamically typed languages such as Groovy, JRuby, Jython to JVM bytecodes, a new bytecode called invokedynamic is being proposed (JSR-292). Note that the details of this JSR will be available only in future. Whatever I say below could be (or could become) completely wrong!
invokedynamic will (probably) not require the target class name and the
method signature. invokedynamic will search the specified method on the
target object based using just the name. The way to handle method overloading
will have to be specified by JSR]. How to handle failure to find required method?
- again this will also need to be specified by the JSR (For example, throw NoSuchMethodException or
call a pre-defined doesNotUnderstand or handleMethodCall method).
Let us consider the case of Groovy.
(JSR-241). Groovy supports
a flexible method dispatching framework. Without going into details:
if a method to be called is found in the class, then a special invokeMethod
method is called. I wrote about that in an
earlier post. Groovy would be able to make
use of invokedynamic to speed-up it's Meta Object Protocol
implementation.
class Person {
public Object invokeMethod(String methodName, Object args) {
System.out.println("calling " + methodName);
return methodName.toUpperCase();
}
};
Person b = new Person();
// This works. Calls "invokeMethod" because there is no "name"
// method in Person class.
System.out.println(b.name());
While Groovy's method dispatch is flexible for "clients" of classes,
calling a super class method is not so flexible. For example, when
super.foo() is called from a Groovy class, it is compiled
much like Java - i.e., using invokespecial. So, method
searched has to be found at "compile time". This means the following
will not work.
class Person {
public Object invokeMethod(String methodName, Object args) {
System.out.println("calling " + methodName);
return methodName.toUpperCase();
}
};
class Employee extends Person {
public String name() {
// This does *not* work! there is no "name" method in Person!
// Ideally, one would expect super.name() to call Person.invokeMethod().
return "Employee " + super.name();
}
}
We get the following "compile time" error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed,
General error during class generation: No such method: name for class: Employee.
Node: org.codehaus.groovy.ast.expr.MethodCallExpression. At [11:40] t.groovy
1 error
A more flexible language like Smalltalk will allow the user
to call super class methods even not found during compilation. If the method
is not found in super class at runtime (or it's super class and so on), then doesNotUnderstand
method will be called [or should I say, doesNotUnderstand
message will be sent!]
But, because Groovy is limited by JVM can do, currently it uses either Java reflection or bytecode generation to implement it's meta object protocol. Java reflection does not allow us to call a overriden super class method on an object. java.lang.reflect.Method.invoke's javadoc says: If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, Second Edition, section 15.12.4.4; in particular, overriding based on the runtime type of the target object will occur.
Similarly, the reflection speed-up classes generated by Groovy can not call super class methods. [I am not sure why Groovy folks do this reflection speed-up -- HotSpot JVM already speeds up reflective calls by bytecode generation internally!]
The only way to call super class method is to generate invokespecial
which can only be inside a subclass! (and not in an unrelated reflection "speed-up" class). This explains why Groovy has to translate super.foo() with
invokespecial instruction. Groovy could probably do
the following to compile super.foo():
invokespecial
invokeMethod method.
But the problem here is that the above scheme "fixes" method dispatching
during compilation. For example, if a super class introduced "foo" method
after the compilation of subclass then that new method won't be called! (invokeMethod would be called. So, do we probably need invokespecialdynamic?
invokespecialdynamic would be like invokespecial
except that
invokedynamic would do!)
invokedynamic
-like throwing exception or calling a special method)
With invokespecialdynamic, Groovy would be able to flexibly implement
super.foo() calls.
Perhaps a less cumbersome name might be invokesuper. I am not familiar with the original decisions that called for invokespecial to call private methods, but it may not be necessary when considering dynamic invocation. If you want to do a super call from within a dynamic method, you will generate an invokesuper. This would mimic how most dynamic languages handle super calls...they are a unique node in the AST or a bytecode in the machine. So generally there's two types of calls that are dynamic:
- dynamically calling method X against a given object, and
- calling super method X from within the original method X
invokedynamic and invokesuper. This is no different than what you describe, however, so I guess I'm still agreeing with you :)
Posted by Charles Oliver Nutter on September 02, 2006 at 04:19 AM IST #
The MetaClass determines the behaviour of the object. The properties, fields and methods on the object are generally accessible via the MetaClass but if you give an object a custom MetaClass you can add, remove and alter methods, fields and properties. The standard metaClass can alter its behaviour dynamically to allow the implementation of Smalltalk stlye categories (the fact that groovy supports multi threaded execution makes this quite an interesting thing to implement!) The MetaClass is fundamental to the semantics of the language. At the moment the MetaClass sometime uses reflection and sometimes does dynamic bytecode generation but that's just an implementation detail (though the performance differences are interesting and might be the subject of a different discussion!)
The MetaClass implements our Meta Object Protocol (MOP). The invokeMethod, etc. methods on Groovy objects is just a convenience feature to allow the programmer to add dynamic behaviour without having to write and register a custom MetaClass. At the moment our MOP is not powerful enough to express all the semantics wee need. We have designed and are implementing a new MOP which, we believe, will fix this problem. So we are adding invokeThisMethod and invokeSuperMethod to the existing invokeMethod functions on the MetaClass. so:
x.foo() -> x.getMetaClass().invokeMethod(x, "foo", new Objetc[]{})
this.foo() -> this.getMetaClass().invokeThisMethod(this, "foo", new Objetc[]{})
super.foo() -> this.getMetaClass().invokeSuperMethod(this, "foo", new Objetc[]{})
The implementation of invokeSuperMethod is hard!
As far as we can see there is no way in the JVM for one class to invoke a super method on another class. There may be a security issue which means that this behaviour is dangerous but we can't think of one. At the moment we have to generate synthetic method on each class to expose the methods which could be called via super. This is not an ideal solution but is the best we can do.
It look as if invokespecialdynamic might help us here. We will follow the progress of this with interest. We would be happy to work with you all to make sure that groovy can benefit from your work.
Posted by John Wilson on September 02, 2006 at 02:06 PM IST #
Posted by A. Sundararajan on September 02, 2006 at 02:49 PM IST #
And one more thing.... Groovy can use both, Reflection and generated classes to make a method call. I would very much like to compare them. Do you have an idea of what kind of test would be suitable for this? Spontaneously I am thinking of something ackermann based, but maybe you have a better idea?
Posted by Jochen Theodorou on September 04, 2006 at 04:21 PM IST #
Posted by A. Sundararajan on September 05, 2006 at 06:46 AM IST #
Posted by jochen Theodorou on September 05, 2006 at 10:18 PM IST #