How does Hotspot JVM handle integer divison overflow on x86?

Viewed 179

There is nothing special about dividing two ints in Java. Unless one of the two special cases handled:

  1. Division by zero. (JVMS requires the virtual machine to throw ArithmeticException)
  2. Division overflow (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.

  • What does the virtual machine do in this case to not lose performance too much in compiled code?
  • Does it exploit the signal handling possibilities in POSIX systems? If so what does it use on Windows?
2 Answers

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).

What does the virtual machine do in this case to not lose performance too much in compiled code?

They don't do anything. It's just implemented as an if statement.

There are different bytecode interpreters based on the target architecture, but I looked and the implementation is the same for all of them. Here's x86

inline jint BytecodeInterpreter::VMintDiv(jint op1, jint op2) {
    /* it's possible we could catch this special case implicitly */
    if ((juint)op1 == 0x80000000 && op2 == -1) return op1;
    else return op1 / op2;
}

I'm not sure what the comment is alluding to. I couldn't find any interesting mentions of this method on the JDK mailing list, which is my usual go-to if I want an explanation for some historic decision.

Anyway, emphasis on the word 'could'. Whatever they meant by it, they don't do it.

Related