saga
saga

Reputation: 2113

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?

Upvotes: 7

Views: 2097

Answers (3)

martin
martin

Reputation: 2194

Another common use-case for dup is array initialisation.

Consider the code int[] a = new int[] {1, 2, 3}.

The iastore instruction stores an integer into an array. It requires three values on the stack: a reference to the array, the index in that array and the value to store and, crucially, it pops all three values after it's called:

The arrayref, index, and value are popped from the operand stack. Java Virtual Machine Specification

A naive way to translate the above example into byte code could look like this:

iconst_3
newarray       int
iconst_0               # array index
iconst_1               # value
iastore                # a[0] = 1
aload_1                # load array ref again
iconst_1
iconst_2
iastore                # a[1] = 2
aload_1                # load array ref yet again
iconst_2
iconst_3
iastore                # a[2] = 3
...
astore_1

Note, that the reference to the array gets reloaded for every iastore instruction.

This can be avoided using dup. Before pushing the index and the value to the stack, we duplicate the array reference on the stack which leaves the bottom entry there to be re-used for the next iastore instruction:

iconst_3
newarray       int
dup
iconst_0
iconst_1
iastore                # a[0] = 1
dup
iconst_1
iconst_2
iastore                # a[1] = 2
dup
iconst_2
iconst_3
iastore                # a[2] = 3
...
astore_1

Upvotes: 0

Holger
Holger

Reputation: 298459

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.

Upvotes: 3

Michael Kay
Michael Kay

Reputation: 163498

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.

Upvotes: 3

Related Questions