Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use cases of jvm dup instruction

Java bytecode instruction set provides various forms of dup instruction. I'm having trouble understanding how these instructions and the swap instruction might be useful. What java code would produce bytecode with these instructions when compiled?

like image 930
saga Avatar asked Feb 20 '19 07:02

saga


2 Answers

Variants of dup can appear in ordinary Java code.

E.g. as elaborated in this answer, object instantiation typically uses dup, as new Object() gets compiled to

new #n              // n referencing Class java.lang.Object in the pool
dup
invokespecial #m    // m referencing Method java.lang.Object.<init>()V

Further, intArray[expression]++ gets compiled to

… (code pushing the results of intArray and expression)
dup2
iaload
iconst_1
iadd
iastore

and, a bit fancier

public static long example3(Long[] array, int i, long l) {
    return array[i]=l;
}

compiles to

   0: aload_0
   1: iload_1
   2: lload_2
   3: invokestatic  #3  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
   6: dup_x2
   7: aastore
   8: invokevirtual #4  // Method java/lang/Long.longValue:()J
  11: lreturn

Changing the array type to long[] produces an example of dup2_x2

As discussed in this Q&A, javac does never use swap or nop (in the current implementation). But just because javac does not use a particular instruction, you can not assume that no compiler uses it.

E.g. there are other Java compilers, like ECJ, but there can be class files created by other programming languages or being already the result of an instrumentation tool, which becomes relevant when you want to instrument code at runtime. And future versions of javac also can use instructions they didn’t use before, just like before Java 8, Java code did not use invokedynamic.

This discussion points to a scenario, where swap would be appropriate. When using try-with-resource, there will be generated code, handling an exception caught while there is already a caught exception. Current javac compiles it (basically) to

astore        n
aload         o
aload         n
invokevirtual #x // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V

where o is the old variable holding the already caught exception and n will be an entirely new variable, which would not be needed when compiling this to

aload         o
swap
invokevirtual #x // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V

instead. So it’s not like these instruction were never-needed exotic constructs. It’s just an implementation detail when a particular code generator doesn’t use them.

Speaking of Instrumentation, it’s also important to keep in mind that a ClassFileTransformer is not guaranteed to receive exactly the same bytecode as produced by the compiler. It might be an equivalent byte code.

So the bottom line is, if you want to implement a ClassFileTransformer, you should be prepared to handle every legal byte code.

like image 127
Holger Avatar answered Sep 26 '22 23:09

Holger


I don't know when javac uses it, but when we generate code we use DUP and SWAP a lot. For example if you're doing the equivalent of

x.setCharm(y);
x.setSpin(z);

then you would load x and immediately DUP it, because invoking the first method will take it off the stack and you want to use it twice.

SWAP comes in handy when you're doing something like

y = x.getCharm();
z.setCharm(y);

where the first instruction leaves y on top of stack, you then stack z, and SWAP, so you now have the right values on stack to invoke the second instruction.

like image 21
Michael Kay Avatar answered Sep 26 '22 23:09

Michael Kay