Основной reference по набору команд преобразованный в HTML.
Reference по наборам команд MMX, SSE и AVX на сайте Intel.
Операции над вещественными числами выполняются отдельными блоками процессора. Исторически сложилось, что для вещественной арифметики использовался отдельный сопроцессор, а начиная с процессоров 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 // операции над целочисленными операндами
В современных 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 можно использовать как обычные скалярные, что намного эффективнее, чем 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
Все вещественные аргументы передаются в функцию через стек.
Возвращаемое вещественное значение должно быть сохранено
в регистре st(0)
, причем это требование необходимо
соблюдать даже при использовании регистров SSE. Это
необходимо для того, чтобы вызывающая функция могла
использовать вещественный результат, независимо от способа
реализации вызываемой функции.
Переместить результат из регистра SSE в регистр x87 можно только с ипользованием памяти:
sub esp, 8 // выделяем 8 байт на стеке
movsd [esp], xmm0 // копируем из xmm0 в память
fld qword ptr [esp] // загружаем из памяти в стек x87
add esp, 8 // освобождаем память на стеке
Между регистрами можно выполнять векторные операции, то есть операции сразу над несколькими 8, 16, 32 или 64-битными значениями, которые хранятся в паре 128-битных регистров.
Общий вид таких команд следующий:
OPERATION p [s|d]
где OPERATION
- это одна из операций add
, mul
и т.д.,
буква p
в названии команды является сокращением от
p
acked, а s
или d
- это s
ingle или d
ouble точность
вещественных чисел.
Загрузка/сохранение выполняется вариантами команды mov
:
mov[ap|up][s|d] DST, SRC
где ap
- загрузка/сохнанение из памяти, выровненной по
границе размера регистра (16 байт), up
- для
невыровненной памяти.
Использование операндов в памяти для операций, отличных от
mov
, возможно только для выровненной памяти.
Для задействования векторных инструкций не обязательно
использовать язык ассемблера. Компиляторы Intel и gcc
имеют поддержку псевдо-функций, объявленных в заголовочных
файлах вида *intrin.h
, которые транслируются в эти
инструкции при компиляции. Подробный Reference
доступен здесь.