0

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.

glee8e
  • 6,180
  • 4
  • 31
  • 51
  • Your premise “*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*” makes no sense. There is never a need to look through super interfaces at all. All you have to know is whether the declared type of the reference (or its inferred type) is an interface or not. Or when you deliberately use a different declaring type for the method, it only matters whether this actual chosen declaring type is an interface or not. – Holger Feb 01 '16 at 12:21
  • @Holger well my fault, I'm a bit confused with the word "delaring", always. – glee8e Feb 02 '16 at 11:55

1 Answers1

2

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.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • I can’t follow your explanation and the example is not helpful either. The compiler will reject your code example, if `A` does not implement `Foo` and has no `fooMethod()` declaration. And the verifier will reject an invocation of `Foo.fooMethod()` if the inferred type is `A` and it still doesn’t implement `Foo` at runtime. The properties of `B` are completely irrelevant for these rules. – Holger Feb 01 '16 at 12:07
  • @Holger The compiler will reject it but the JVM won't. Like I said, the JVM only type checks interfaces at runtime. – Antimony Feb 01 '16 at 16:02
  • Of course, the JVM checks only at runtime. I guess, you wanted to say that it checks at invocation time rather than verification time, however, even if the JVM exhibits such a behavior, that doesn’t imply that the specification guarantees such behavior. But your statement “there is no way to statically know whether a implements Foo” is only valid if such a dynamic behavior is mandated. Otherwise, there is a way—just implement the same formal rules in the JVM as used javac. – Holger Feb 01 '16 at 16:08
  • @Holger, I just added another example which demonstrates why the JVM can't apply the same checks that the compiler does. – Antimony Feb 01 '16 at 16:12
  • @Holger: P.S. It doesn't matter if the behavior is mandated or not. What matters is the behavior of Sun's very first JVM, because the instruction set was obviously designed to make Sun's implementation easier and more efficient. The rest is just backwards compatibility. – Antimony Feb 01 '16 at 16:15
  • Your second example just perfectly demonstrates why stackmaptables were introduced. Before them, the JVM had indeed to infer a common type for `B` and `C` which is an expensive operation, especially as the JVM doesn’t know in advance that just `Foo` is required rather than `A & Foo`. (Fun fact: it will do it, even when it doesn’t check the subsequent invocation). Nowadays, the stackmaptable explicitly declares that the merged type is `Foo`, so the verifier only has to check whether `B` is a subtype of `Foo` and whether `C` is a subtype of `Foo`, thus the complexity hasn’t changed. – Holger Feb 01 '16 at 16:21
  • Yeah, “backward compatibility” is a much better explanation than pretending that the verification was impossible. Especially today, where both, `invokespecial` and `invokevirtual` also may refer to interface (default) methods, rendering the distinction obsolete. – Holger Feb 01 '16 at 16:23
  • It was originally infeasible (effectively impossible) and the decision persists due to backwards compatibility. – Antimony Feb 01 '16 at 16:23
  • “*It was originally infeasible … and the decision persists due to backwards compatibility*”—that’s the summary that should appear at the beginning of your answer. – Holger Feb 01 '16 at 19:02
  • excellent explaination! sorry for not accepting your anwser immediately. I'm yet new to SO, and that little grey tick just didn't show up in my mobile(I turned off the images), thus making me to think I can't accept an anwser anymore if the question is duplicate. – glee8e Feb 02 '16 at 13:16