Skip to content

Latest commit

 

History

History
 
 

x86_fpmath

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Вещественная арифметика на x86

Основной reference по набору команд преобразованный в HTML.

Reference по наборам команд MMX, SSE и AVX на сайте Intel.

Сопроцессор x87

Операции над вещественными числами выполняются отдельными блоками процессора. Исторически сложилось, что для вещественной арифметики использовался отдельный сопроцессор, а начиная с процессоров 486 (1991 год) этот сопроцессор был интегрирован в кристалл основного процессора.

Таким образом, в целях совместимости со старым кодом, выполнение вещественных операций над числами с плавающей точкой выполняется из предположения наличия сопроцессора.

Компилятор gcc использует по умолчанию именно этот способ работы с вещественными числами для 32-разрядной архитектуры (эквивалентно опции -mfpmath=387). Для 64-разрядной архитектуры по умолчанию используется набор команд SSE (-mfpmath=sse).

Взаимодействие с сопроцессором x87 организовано в форме записи операндов в стек и выполнения операций над элементами этого стека. Команды сопроцессора обычно начинаются с буквы f, и оперируют с регистрами, которые обозначаются от st(0) (вершина стека) до st(7) (последний регистр FPU).

Выполнять арифметические FPU-операции можно над любыми регистрами этого стека, но операции, позволяющие взаимодействие c памятью, возможны только через вершину стека st(0).

Основные инструкции x86:

fld SIZE ptr ADDR // загрузить в стек значение из памяти
fld REG           // поместить на вершину стека значение из другого регистра st(X)
fld1              // поместить на вершину стека значение 1
fldz              // 0
fldpi             // π
fst SIZE ptr ADDR // сохранить из стека в память

fild SIZE ptr ADDR // загрузить целое число в стек
fist SIZE ptr ADDR // сохранить целое число из стека

fcom              // сравнение st(0) с памятью
fcomi             // сравнение st(0) с st(i)

fadd
fsub
fmul
fdiv              // операции над вещественными операндами

fiadd
fisub
fimul
fidiv             // операции над целочисленными операндами

Регистры MMX/SSE/AVX/AVX-512

В современных Intel/AMD процессорах есть 16 регистров (в 32-разрядном режиме доступны только 8), которые предназначены как для вещественных операций, так и для целочисленных.

128-битные регистры MMX/SSE именуются xmm0...xmm7, xmm8...xmm15.

256-битные регистры AVX ymm0...ymm15 подразумевают, что их младшие 128 бит совпадают с регистрами MMX/SSE.

512-битные регистры AVX-512 (новые Xeon и Core i9) zmm0...zmm15 подразумевают, что младшие 256 бит совпадают с регистрами AVX.

Скалярные инструкции SSE

Несмотря на свой большой размер, регистры SSE можно использовать как обычные скалярные, что намного эффективнее, чем x87 FPU.

В отличии от регистров в стеке st(0)...st(7), все регистры SSE являются равнозначными.

// Копирование регистр-регистр и регистр-память
movsd   DST, SRC  // пересылка double
movss   DST, SRC  // пересылка float

// Арифметические
addsd   DST, SRC   // DST += SRC, double
addss   DST, SRC   // DST += SRC, float
subsd   DST, SRC   // DST -= SRC, double
subss   DST, SRC   // DST -= SRC, float
mulsd   DST, SRC   // DST *= SRC, double
mulss   DST, SRC   // DST *= SRC, float
divsd   DST, SRC   // DST /= SRC, double
divss   DST, SRC   // DST /= SRC, float
sqrtsd  DST, SRC   // DST = sqrt(SRC), double
sqrtss  DST, SRC   // DST = sqrt(SRC), float
maxsd   DST, SRC   // DST = max(DST, SRC), double
maxss   DST, SRC   // DST = max(DST, SRC), float
minsd   DST, SRC   // DST = min(DST, SRC), double
minss   DST, SRC   // DST = min(DST, SRC), float

// Преобразования
cvtsd2si DST, SRC  // double -> int
cvtsi2sd DST, SRC  // int -> double

// Сравнения (операция DST-SRC, которая меняет флаги)
comisd  DST, SRC  // для double
comiss  DST, SRC  // для float

Соглашения о вызовах для x86 (32 бит)

Все вещественные аргументы передаются в функцию через стек. Возвращаемое вещественное значение должно быть сохранено в регистре st(0), причем это требование необходимо соблюдать даже при использовании регистров SSE. Это необходимо для того, чтобы вызывающая функция могла использовать вещественный результат, независимо от способа реализации вызываемой функции.

Переместить результат из регистра SSE в регистр x87 можно только с ипользованием памяти:

sub     esp, 8          // выделяем 8 байт на стеке
movsd   [esp], xmm0     // копируем из xmm0 в память
fld     qword ptr [esp] // загружаем из памяти в стек x87
add     esp, 8          // освобождаем память на стеке

Векторные инструкции SSE и intrisics-функции на Си

Между регистрами можно выполнять векторные операции, то есть операции сразу над несколькими 8, 16, 32 или 64-битными значениями, которые хранятся в паре 128-битных регистров.

Общий вид таких команд следующий:

OPERATION p [s|d]

где OPERATION - это одна из операций add, mul и т.д., буква p в названии команды является сокращением от packed, а s или d - это single или double точность вещественных чисел.

Загрузка/сохранение выполняется вариантами команды mov:

mov[ap|up][s|d]   DST, SRC

где ap - загрузка/сохнанение из памяти, выровненной по границе размера регистра (16 байт), up - для невыровненной памяти.

Использование операндов в памяти для операций, отличных от mov, возможно только для выровненной памяти.

Для задействования векторных инструкций не обязательно использовать язык ассемблера. Компиляторы Intel и gcc имеют поддержку псевдо-функций, объявленных в заголовочных файлах вида *intrin.h, которые транслируются в эти инструкции при компиляции. Подробный Reference доступен здесь.