Hotspot JVM 如何处理 x86 上的整数除法溢出?

cac*_*co3 3 java jvm jvm-hotspot

intJava中将两个s相除并没有什么特别之处。除非处理两种特殊情况之一:

  1. 被零除。(JVMS 需要虚拟机抛出ArithmeticException
  2. 除法溢出(Integer.MIN_VALUE / -1,JVMS 要求结果等于Integer.MIN_VALUE)(这个问题只针对这种情况)

来自第 6 章 Java 虚拟机指令集。idiv

有一种特殊情况不满足此规则:如果被除数是该int类型的最大可能数量级的负整数,并且除数为-1,则发生溢出,结果等于被除数。尽管溢出,但在这种情况下不会引发异常。

在我的计算机上 ( x86_64) 本机分区产生SIGFPE错误。

当我编译以下 C 代码时:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我得到结果(在 x86 上):

tmp $ gcc division.c 
tmp $ ./a.out 
Floating point exception (core dumped)
Run Code Online (Sandbox Code Playgroud)

在 ARM ( aarch64)上编译的完全相同的代码产生:

-2147483648 / -1 = -2147483648
Run Code Online (Sandbox Code Playgroud)

因此,在 x86 上,Hotspot VM 似乎需要做额外的工作来处理这种情况。

  • 在这种情况下,虚拟机如何做才能在编译代码中不损失太多性能?
  • 它是否利用了 POSIX 系统中信号处理的可能性?如果是这样,它在 Windows 上使用什么?

apa*_*gin 6

你是对的 -idiv由于特殊情况,HotSpot JVM 不能盲目使用cpu 指令。

因此 JVM 执行额外的检查,是否Integer.MIN_VALUE除以-1。这种检查存在于解释器编译代码中

如果我们用 来检查实际编译的代码-XX:+PrintAssembly,我们会看到类似的东西

  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)
Run Code Online (Sandbox Code Playgroud)

然而,正如您可能注意到的,没有检查除数 == 0。这被认为是一种例外情况,在正常程序中永远不会发生。这称为隐式异常。JVM 会记录此类异常可能发生的位置,并依靠操作系统信号(或 Windows 术语中的异常)来处理这种情况。

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);
Run Code Online (Sandbox Code Playgroud)

但是,如果在同一位置发生隐式异常过于频繁,JVM 会取消优化已编译的代码,然后使用显式零检查重新编译它(以避免频繁信号处理的性能损失)。