I'm seeking clarity on how the invokedynamic
instruction operates in Java bytecode. To illustrate, I've prepared a simple Java code snippet utilizing lambda expressions, which typically leads to the generation of the invokedynamic
instruction upon compilation:
public class App{
public static void main(String... args){
Runnable r = () -> {};
r.run();
}
}
Upon compilation with javac
, the following bytecode is produced:
0: invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #11, 1 // InterfaceMethod java/lang/Runnable.run:()V
12: return
As I understand, after the invokedynamic
instruction, the operand stack should contain a CallSite
(the result of the bootstrap method), which is subsequently stored onto the operand stack. Then, this CallSite
is stored into a variable and executed.
However, the description of the invokedynamic
method on Wikipedia states:
[invokedynamic instruction] invokes a dynamic method and puts the result on the stack (might be void);
This statement, particularly "might be void" has left me confused. Does this imply that a bootstrap method may not necessarily return a CallSite
at all? Despite my attempts, I haven't been able to find any Java code snippet that generates the invokedynamic
instruction without placing any values on the operand stack.
Could someone clarify whether it's possible for the invokedynamic
instruction to not place anything onto the operand stack, including the CallSite
? I've reviewed related answers on StackOverflow like this, this, and this, but none explicitly address the state of the operand stack. Additionally, I've attempted to find a bytecode debugger to check the operand stack state, but to no avail.
Any insights or guidance on this matter would be greatly appreciated.
You misunderstand what invokedynamic
does.
invokedynamic
does not push a CallSite
onto the stack. The operands of the invokedynamic
instruction refers to a method that returns a CallSite
. In the case of lambda expressions, this is the LambdaMetafactory.metafactory
method.
invokedynamic
calls this CallSite
-returning method (metafactory
), gets a CallSite
, then calls the target method handle indicated by CallSite
. If this method handle needs arguments, things on the operand stack are popped. Finally, the result of calling this method handle is pushed onto the stack.
In this case, the call site returned by metafactory
refers to some method that does not take arguments, and returns an implementation of Runnable
. invokedynamic
pushes this Runnable
onto the stack. It is then stored into a local variable, loaded from that same variable (astore
, aload
), and its run
method is called with invokeinterface
.
metafactory
takes a few arguments, and you might be wondering how invokedynamic
knows what arguments to pass. Part of it is just "built into" invokedynamic
, and the rest is stored in the BootstrapMethods
attribute of the class file.
Roughly translating this into Java code, the whole process looks like this:
Runnable r = LambdaMetafactory.metafactory(XXX)
.getTarget().invokeExact();
where the arguments "XXX" are in the BootstrapMethods
attribute.
As another example, string concatenation uses invokedynamic
too. This time, the operands will refer to one of the methods in StringConcatFactory
. The method will return a CallSite
representing some method that will return the concatenated string. For an expression like "Foo" + System.nanoTime() + "Bar"
, invokedynamic
would do something like:
StringConcatFactory.makeConcatWithConstants(XXX)
.getTarget().invokeExact(System.nanoTime())
For more info, see the JVMS.