Search code examples
javajvmbytecodeinvokeinvokevirtual

Is there any reason for not invokevirtual and invokeinteface bytecode instruction into one?


Is there any reason for making the instruction to invoke non-static non-constructor method into two distinct instruction instead of one unified instruction, like invokeinstance? Does it has anything to do with some random internal JVM mechanism or it's yet another horrific legacy issue?

I know we have invokespecial because invoking constructor needs name ckecking, marking one other constructor has been executed, etc, and invokestatic because we don't need an objectref dumped into the new stack frame. It, however, doesn't has an easy-to-find reason why Sun opt to have the possible universal instruction spilted into invokevirtual and invokeinterface. Without spliting it, the ASM code could be a lot simpler since we don't have to look through the all superinterfaces to see if this is an interface method, building up the code conplexity.


Solution

  • Invokeinterface is different because interfaces are only type-checked at runtime. With a virtual method, you can statically determine that the type is a subtype of the class where the method is defined. For an interface, it is impossible to know for sure whether the value has a type that implements that interface without knowing the runtime type of the value.

    Consider the following pseudocode (note that this isn't allowed in Java, but the bytecode equivalent is allowed by the JVM)

    class A
    class B extends A implements Foo
    
    A a = new B()
    a.fooMethod()
    

    There is no way to statically know whether a implements Foo or not because the static type A doesn't implement Foo but the actual runtime type B does.

    Edit: The above example will be rejected by the Java compiler, but not the JVM. You might wonder why the JVM doesn't just apply the same rules as the compiler. The difference is that the JVM doesn't have source level type information about local variables. Consider the following example, which is allowed in Java.

    class A
    class B extends A implements Foo
    class C extends A implements Foo
    
    Foo x = null;
    if (whatever) {
    x = new B();
    } else {
    x = new C();
    }
    x.fooMethod();
    

    The JVM doesn't know the intended type of x (without stackmaptables, which weren't introduced until much later), so it infers the type of x to be A, which doesn't implement Foo. Therefore, if it tried to statically check interfaces as verification time, it would reject valid Java code! The only feasible solution is to not typecheck interfaces.

    In order to safely check interfaces, the JVM would have to be able to infer types like "subclass of A which also implements Foo", which obviously adds an enormous amount of complexity to something which has to be fast and efficient. So it makes sense that the designers didn't go this route.

    P.S. Invokespecial isn't just for constructors - it's also used for private and super method calls. Most likely it was originally a separate instruction as an optimization, since the invoked method is known at load time instead of varying with the runtime type of the target. In fact, it was originally called invokenonvirtual.