Ответ 1
Мне кажется, из очень тривиального тестового примера, как JVM round-trip каждые double
вычисление через память, чтобы получить округление, которое он хочет. Похоже, что он делает что-то странное с парой волшебных констант. Вот что он сделал для меня для простой "вычислительной 2 ^ n наивной" программы:
0xb1e444b0: fld1
0xb1e444b2: jmp 0xb1e444dd ;*iload
; - fptest::[email protected] (line 6)
0xb1e444b7: nop
0xb1e444b8: fldt 0xb523a2c8 ; {external_word}
0xb1e444be: fmulp %st,%st(1)
0xb1e444c0: fmull 0xb1e44490 ; {section_word}
0xb1e444c6: fldt 0xb523a2bc ; {external_word}
0xb1e444cc: fmulp %st,%st(1)
0xb1e444ce: fstpl 0x10(%esp)
0xb1e444d2: inc %esi ; OopMap{off=51}
;*goto
; - fptest::[email protected] (line 6)
0xb1e444d3: test %eax,0xb3f8d100 ; {poll}
0xb1e444d9: fldl 0x10(%esp) ;*goto
; - fptest::[email protected] (line 6)
0xb1e444dd: cmp %ecx,%esi
0xb1e444df: jl 0xb1e444b8 ;*if_icmpge
; - fptest::[email protected] (line 6)
Я считаю, что 0xb523a2c8
и 0xb523a2bc
являются _fpu_subnormal_bias1
и _fpu_subnormal_bias2
из исходного кода точки доступа. _fpu_subnormal_bias1
выглядит как 0x03ff8000000000000000
, а _fpu_subnormal_bias2
выглядит 0x7bff8000000000000000
. _fpu_subnormal_bias1
имеет эффект масштабирования наименьшей нормальной double
до наименьшей нормальной long double
; если FPU округляется до 53 бит, произойдет "правильная вещь".
Я бы предположил, что существует, казалось бы, бессмысленная инструкция test
, чтобы поток можно было прервать, пометив эту страницу нечитаемой в случае необходимости GC.
Здесь код Java:
import java.io.*;
public strictfp class fptest {
public static double calc(int k) {
double a = 2.0;
double b = 1.0;
for (int i = 0; i < k; i++) {
b *= a;
}
return b;
}
public static double intest() {
double d = 0;
for (int i = 0; i < 4100; i++) d += calc(i);
return d;
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++)
System.out.println(intest());
}
}
Копаем дальше, код для этих операций находится на виду в коде OpenJDK в hotspot/src/cpu/x86/vm/x86_63.ad
. Соответствующие фрагменты:
instruct strictfp_mulD_reg(regDPR1 dst, regnotDPR1 src) %{
predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
->method()->is_strict() );
match(Set dst (MulD dst src));
ins_cost(1); // Select this instruction for all strict FP double multiplies
format %{ "FLD StubRoutines::_fpu_subnormal_bias1\n\t"
"DMULp $dst,ST\n\t"
"FLD $src\n\t"
"DMULp $dst,ST\n\t"
"FLD StubRoutines::_fpu_subnormal_bias2\n\t"
"DMULp $dst,ST\n\t" %}
opcode(0xDE, 0x1); /* DE C8+i or DE /1*/
ins_encode( strictfp_bias1(dst),
Push_Reg_D(src),
OpcP, RegOpc(dst),
strictfp_bias2(dst) );
ins_pipe( fpu_reg_reg );
%}
instruct strictfp_divD_reg(regDPR1 dst, regnotDPR1 src) %{
predicate (UseSSE<=1);
match(Set dst (DivD dst src));
predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
->method()->is_strict() );
ins_cost(01);
format %{ "FLD StubRoutines::_fpu_subnormal_bias1\n\t"
"DMULp $dst,ST\n\t"
"FLD $src\n\t"
"FDIVp $dst,ST\n\t"
"FLD StubRoutines::_fpu_subnormal_bias2\n\t"
"DMULp $dst,ST\n\t" %}
opcode(0xDE, 0x7); /* DE F8+i or DE /7*/
ins_encode( strictfp_bias1(dst),
Push_Reg_D(src),
OpcP, RegOpc(dst),
strictfp_bias2(dst) );
ins_pipe( fpu_reg_reg );
%}
Я ничего не вижу для сложения и вычитания, но я бы сказал, что они просто делают добавление/вычитание с FPU в 53-битном режиме, а затем округляют результат через память. Мне немного любопытно, есть ли сложный случай переполнения, что они ошибаются, но мне не любопытно узнать.