One of the more interesting bytecode instructions is invokespecial. During
Java's early days this instruction was called invokenonvirtual, but in JDK
1.0.2 it was renamed to invokespecial to indicate it has some very special
semantics.
Invokespecial is used in three ways, to call instance initializers (constructors), to
call base class methods non-virtually and to call private methods. It's worth mentioning
that, unlike the CLR call instruction, invokespecial cannot be used
to call arbitrary methods, it can only call methods in the current class or in a base
class of of the current class (and then only on references of the type of the current
class, or subclasses of it). The JVM's invokevirtual is very similar to the
CLR's callvirt instruction. In addition to invokespecial and invokevirtual,
the JVM also has invokestatic and invokeinterface to invoke static methods
and interface methods, respectively. The CLR has no special instructions for that,
it uses the call and callvirt instructions to call static and interface
methods.
Prior to JDK 1.1, invokespecial called the exact method specified in the instruction.
In JDK 1.1 this behavior was changed, because it caused versioning problems. Here
is an example of what could go wrong:
Component A - version 1
public
class GrandParent
{
protected void myMethod()
{
//
...
}
}
public
class Parent extends GrandParent
{
}
Component B
public
class Child extends Parent
{
protected void myMethod()
{
//
...
super.myMethod();
}
}
The compiler would compile the super.myMethod() call
in Child to invokespecial GrandParent.myMethod().
Now suppose a new version of Component A is released:
Component A - version 2
public
class GrandParent
{
protected void myMethod()
{
//
...
}
}
public
class Parent extends GrandParent
{
protected void myMethod()
{
//
...
super.myMethod();
}
}
When Component B is used (without recompiling) with this new version of Component
A, the super.myMethod call
in Child will still go directly to GrandParent.myMethod and
this is probably not what the author of Component A had intended.
To fix this, invokespecial was changed to search the class hierarchy if the
called class is a base class of the caller (from the caller's base class on up), but
only if the caller's class has the ACC_SUPER bit set in the class' access_flags mask.
All Java compilers since JDK 1.1 always set the ACC_SUPER flag. It's interesting to
note that the current Sun JRE 1.4.1 still honors a cleared ACC_SUPER flag.
Why doesn't the CLR have an equivalent of the ACC_SUPER flag? The reason is that it
isn't needed. When, for example, the C# compiler compiles a base method call, it emits
a call instruction to the immediate base class of the caller, even
if that class isn't the one that implements the called method[1]. When the JIT
is resolving the call it searches up the class hierarchy to find the actual method.
Comparison JVM and CLR call instructions
JVM
|
CLR
|
Notes
|
invokespecial
|
call
|
invokespecial checks the object reference for null, call doesn't.
|
invokevirtual
|
callvirt
|
|
invokeinterface
|
callvirt
|
Like in other places where the JVM consumes interface references, the object reference
doesn't have to statically implement the called interface type.
|
invokestatic
|
call
|
|
One final issue relevant to IKVM here is that invokespecial requires the
object reference to be checked for null, the CLR call instruction doesn't do
this (for this reason the C# compiler uses callvirt when calling non-virtual
methods, except when explicitly calling a base class method of course, it always makes
sure the object reference isn't null). The IKVM compiler has to insert an explicit
check for null references when it is compiling invokespecial. It took me a
while to come up with an efficient way of doing this. Javac faces a similar issue
when it compiles the explicit instantation of an inner class:
public
class Foo {
public
class Inner {}
static
void method() {
//
next line throws a NullPointerException
((Foo)null).new Inner();
}
}
Why does this throw a NullPointerException? The answer may be surprising, but it is
because Javac inserts a call to ((Foo)null).getClass() to
make it throw a NullPointerException if the outer this is null. If it didn't do this,
you'd run into a NullPointerException later on when one of Inner's methods tried to
use the outer this and this would be a very surprising and hard to find bug. However,
I didn't want to use this trick because it isn't particularly efficient. What I came
up with is the following trick:
ldvirtftn
instance string System.Object::ToString()
pop
The JIT compiles this to:
mov eax,dword
ptr [ecx]
mov eax,dword
ptr [eax+28h]
If the reference in question is null, the first instruction causes an x86 trap that
the CLR translates into a NullReferenceException. The second instruction is overhead
and hopefully a future version of the JIT will stop emitting it, but it isn't very
expensive anyway.
Note to self: Consider adding an optimization to the IKVM compiler to detect Javac's
null reference check:
invokevirtual
java/lang/Object.getClass()
pop
and replace it with the more efficient ldvirtftn based check above. This could
actually be an important optimization, because getClass() on IKVM is pretty expensive.
[1] When the base classes are in the same module, the C# actually emits a call to
the class the defines the method, instead of to the direct base class. This is an
optimization, because it saves the JIT from having to search for the method in the
class hierarchy. When all classes are in the same module, there obviously aren't any
versioning issues, since the module is always built as one unit.