Сигнализация 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, и может сломать много библиотек.