Сигнализация NaN была повреждена при возврате из функции x86 (flds/fstps из x87)

У меня странное поведение с x86 (32-разрядной) Linux Linux GCC. Я генерирую сигнализацию NaN, используя встроенную в GCC __builtin_nansf(""), который генерирует 0x7fa00000. После возвращения этого значения из функции в виде числа с плавающей запятой оно изменяется на 0x7fe00000. Вот короткий пример:

#include <stdio.h>
float f = __builtin_nansf("");
float y;

float func (void)
{
  return f;
}

int main (void)
{
  printf("%x\n", *((int*)&f));
  y = func();
  printf("%x\n", *((int*)&y));
}

Программа составлена ​​с gcc-4.6.2 program.c, его вывод:

7fa00000
7fe00000

Gdb:

(gdb) p/x f   
$2 = 0x7fa00000
...
(gdb) si
0x08048412 in func ()
1: x/i $pc
0x8048412 <func+14>:    flds   -0x4(%ebp)
(gdb) x/x $ebp-4
0xbfffeb34: 0x7fa00000
(gdb) si
(gdb) info all-regis
st0            nan(0xe000000000000000)  (raw 0x7fffe000000000000000)
... //after return from func
(gdb) si
0x0804843d in main ()
1: x/i $pc
0x804843d <main+38>:    fstps  0x804a024
(gdb) si
(gdb) x/x 0x804a024
0x804a024 <y>:  0x7fe00000

Почему моя сигнализация NaN модифицирована? Как я могу предотвратить эту модификацию?

2 ответа

Решение

Я не уверен, что вы можете предотвратить это. Загрузка sNaN в x87 обычно вызывает исключение INVALID, а затем преобразует значение в qNaN, устанавливая msb (23-битной) мантиссы. То есть ИЛИ с 0x00400000.

В Руководствах разработчиков программного обеспечения Intel® 64 и IA-32, том 1, 4.8.3.4 описывается обработка sNan/qNan. Глава 8 посвящена программированию X87 FPU. Том 3, 22.18, также описывает, как NaN обрабатываются FPU X87.

Я не вижу битов в контрольном слове X87, которые приведут к желаемому поведению при распространении sNaN.

После поиска в Google по запросу "gcc 7fa00000" я обнаружил ошибку 57484 в bugzilla GCC http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57484 с несколькими полезными комментариями.

Урош Бизжак (сопровождающий порт i386 в GCC) говорит в комментариях 11,12,14 и в последнем, что x86 ABI и x86-32 ABI не предназначены для полной поддержки стандарта IEEE754 на x87 и "проблема, к сожалению, не решаема":

ABI просто не подходит для базового аппаратного обеспечения x87 с точки зрения NaN.

К сожалению, эта проблема не решена. x87 и x86-32 ABI просто не предназначены для обработки всех деталей стандарта IEEE 754.

По словам Уроша, при использовании устаревшего x87 на цели x86 gcc загрузка float и удваивается из памяти в регистры (стек) x87 FP рассматривается как преобразование формата с изменением сигнальных NAN (sNAN) в тихие NAN (qNAN). -msse2 -mfpmath=sse набор опций поможет выполнить все математические вычисления в SSE2, но функция по-прежнему возвращает значение FP через стек x87:

$ gcc-4.6.3 -msse2 -mfpmath=sse test.c -o sse2math.out
$ objdump -d sse2math.out 

./c.out:     file format elf32-i386
...

08048404 <func>:
 8048404:   55                      push   %ebp
 8048405:   89 e5                   mov    %esp,%ebp
 8048407:   83 ec 04                sub    $0x4,%esp
 804840a:   a1 14 a0 04 08          mov    0x804a014,%eax
 804840f:   89 45 fc                mov    %eax,-0x4(%ebp)
 8048412:   f3 0f 10 45 fc          movss  -0x4(%ebp),%xmm0
 8048417:   f3 0f 11 45 fc          movss  %xmm0,-0x4(%ebp)
 804841c:   d9 45 fc                flds   -0x4(%ebp)
 804841f:   c9                      leave  
 8048420:   c3                      ret    

После добавления еще одного варианта -mno-fp-ret-in-387 (полный набор -msse2 -mfpmath=sse -mno-fp-ret-in-387) регистры x87 fp больше не используются для передачи возврата с плавающей точкой:

08048404 <func>:
 8048404:   55                      push   %ebp
 8048405:   89 e5                   mov    %esp,%ebp
 8048407:   a1 14 a0 04 08          mov    0x804a014,%eax
 804840c:   5d                      pop    %ebp
 804840d:   c3                      ret    

Но -mno-fp-ret-in-387 опция изменит ABI, и может сломать много библиотек.

Другие вопросы по тегам