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
invokedynamicdoes.invokedynamicdoes not push aCallSiteonto the stack. The operands of theinvokedynamicinstruction refers to a method that returns aCallSite. In the case of lambda expressions, this is theLambdaMetafactory.metafactorymethod.invokedynamiccalls thisCallSite-returning method (metafactory), gets aCallSite, then calls the target method handle indicated byCallSite. 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
metafactoryrefers to some method that does not take arguments, and returns an implementation ofRunnable.invokedynamicpushes thisRunnableonto the stack. It is then stored into a local variable, loaded from that same variable (astore,aload), and itsrunmethod is called withinvokeinterface.metafactorytakes a few arguments, and you might be wondering howinvokedynamicknows what arguments to pass. Part of it is just "built into"invokedynamic, and the rest is stored in theBootstrapMethodsattribute of the class file.Roughly translating this into Java code, the whole process looks like this:
where the arguments "XXX" are in the
BootstrapMethodsattribute.As another example, string concatenation uses
invokedynamictoo. This time, the operands will refer to one of the methods inStringConcatFactory. The method will return aCallSiterepresenting some method that will return the concatenated string. For an expression like"Foo" + System.nanoTime() + "Bar",invokedynamicwould do something like:For more info, see the JVMS.