There is nothing special about dividing two int
s in Java. Unless one of the two special cases handled:
ArithmeticException
)Integer.MIN_VALUE / -1
, JVMS requires the result to be equal to Integer.MIN_VALUE
) (This question is about this case exclusively).From Chapter 6. The Java Virtual Machine Instruction Set. idiv
:
There is one special case that does not satisfy this rule: if the dividend is the negative integer of largest possible magnitude for the
int
type, and the divisor is-1
, then overflow occurs, and the result is equal to the dividend. Despite the overflow, no exception is thrown in this case.
On my computer (x86_64
) native division produces a SIGFPE
error.
When I compile the following C code:
#include <limits.h>
#include <stdio.h>
int divide(int a, int b) {
int r = a / b;
printf("%d / %d = %d\n", a, b, a / b);
return r;
}
int main() {
divide(INT_MIN, -1);
return 0;
}
I get the result (on x86):
tmp $ gcc division.c
tmp $ ./a.out
Floating point exception (core dumped)
Exactly the same code compiled on ARM (aarch64
) produces:
-2147483648 / -1 = -2147483648
So it seems that on x86 the Hotspot VM is required to do extra work to handle this case.
You are right - HotSpot JVM cannot blindly use idiv
cpu instruction because of the special case.
Hence JVM performs an extra check, whether Integer.MIN_VALUE
is divided by -1
. This check exists both in the interpreter and in the compiled code.
If we check the actual compiled code with -XX:+PrintAssembly
, we'll see something like
0x00007f212cc58410: cmp $0x80000000,%eax ; dividend == Integer.MIN_VALUE?
0x00007f212cc58415: jne 0x00007f212cc5841f
0x00007f212cc58417: xor %edx,%edx
0x00007f212cc58419: cmp $0xffffffff,%r11d ; divisor == -1?
0x00007f212cc5841d: je 0x00007f212cc58423
0x00007f212cc5841f: cltd
0x00007f212cc58420: idiv %r11d ; normal case
0x00007f212cc58423: mov %eax,0x70(%rbx)
However, as you may notice, there is no check for divisor == 0. This is considered an exceptional case, which should never happen in a normal program. This is called an implicit exception. JVM records the place where such exception may happen, and relies on OS signals (or Exceptions in Windows terminology) to handle this case.
See os_linux_x86.cpp:
if (sig == SIGFPE &&
(info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) {
stub =
SharedRuntime::
continuation_for_implicit_exception(thread,
pc,
SharedRuntime::
IMPLICIT_DIVIDE_BY_ZERO);
However, if it happens that an implicit exception occurs too often at the same place, JVM deoptimizes the compiled code, and recompiles it afterwards with the explicit zero check (to avoid performance penalty of frequent signal handling).