static int add(int x, int y) { if (y == 0) return x; return add(x ^ y, (x & y) << 1); }
return
operation. Optimization is to recursively call not to create a new stack frame, and reuse the current one. To do this, put new parameters in place of the old ones and perform an unconditional transition to the first instruction of the method. In pseudocode, it will look like this: static int add(int x, int y) { start: { if (y == 0) return x; (x, y) = (x ^ y, (x & y) << 1); goto start; } }
static int add(int x, int y) { while (true) { if (y == 0) return x; int _x = x ^ y; int _y = (x & y) << 1; x = _x; y = _y; } }
GOTO
we lose a lot of stack frames with information about the entry points that we should have according to the specification.GOTO
, you need to know for sure that in fact the same method should be called. This requirement is met for:final
classes;invokespecial
calls. This option cannot be created by the compiler from Java source code, since invokespecial
is only available for calling methods of the superclass.try
block, then we cannot just take and transfer control instructions outside this block itself. Here is an example: static int f(boolean shouldThrow) { if (shouldThrow) throw new RuntimeException(); try { f(!shouldThrow); } catch (Exception e) { } return -1; }
f(false)
should return -1, but if we make a GOTO
in the place of a recursive call, we will get a RuntimeException
, and this is clearly different from what should happen with the correct optimization.ClassLoader
- embedded in ClassLoader
, changes will be visible only in runtime.static method
/ final method
/ final class
;try
blocks. static int gcd(int n, int m) { try { if (m == 0) return n; } catch (Throwable t) { // do nothing } return gcd(m, n % m); }
static gcd(II)I TRYCATCHBLOCK TryBlockStart TryBlockEnd CatchBlockStart java/lang/Throwable TryBlockStart: ILOAD 1 IFNE ElseBlock ILOAD 0 TryBlockEnd: IRETURN CatchBlockStart: ASTORE 2 // - ElseBlock: ILOAD 1 ILOAD 0 ILOAD 1 IREM INVOKESTATIC Main.gcd(II)I IRETURN
try
block descriptions. Each description has 4 components: the start of the block, the end of the block, the exception handler, and the exception class descriptor. This information allows us to unambiguously determine the instructions that are inside the try
blocks, and not to optimize them.INVOKE*
instructions with a method descriptor that matches the method itself (in this case, the gcd(II)I
descriptor of the Main
class method is searched for), immediately followed by a RETURN
instruction.INVOKESTATIC
must be converted from a call to an unconditional transition. It is known that at the time of the call all the parameters are on the stack. For static methods, everything is simple, you need to save these parameters back to local variables and make an unconditional transition to the very beginning: static gcd(II)I TRYCATCHBLOCK TryBlockStart TryBlockEnd CatchBlockStart java/lang/Throwable StartLabel: TryBlockStart: ILOAD 1 IFNE ElseBlock ILOAD 0 TryBlockEnd: IRETURN CatchBlockStart: ASTORE 2 ElseBlock: ILOAD 1 ILOAD 0 ILOAD 1 IREM ISTORE 1 ISTORE 0 GOTO StartLabel
this
. It is technically possible to find in the bytecode the place of calculation of this object and to carry out optimization only when this calculation is equal to ALOAD 0
.this
(making ASTORE 0
). Strangely enough, this approach works, but I, due to insufficient knowledge, cannot guarantee that the JIT will behave correctly in this situation. I would like to know the answer in the comments - are there any risks here?Source: https://habr.com/ru/post/319282/
All Articles