-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# Ассемблер x86, часть 3 | ||
|
||
## Регистры SSE | ||
|
||
SSE (Streaming SIMD Extension) - набор инструкций, позволяющий выполнять несколько одинаковых | ||
операций одновременно. Набор инструкций SSE продолжает расширятся. | ||
|
||
Для хранения аргументов операций SSE используются регистры xmm. 32-битная система команд | ||
x86 позволяет использовать 8 регистров %xmm0 ... %xmm7. 64-битная система команд x64 позволяет использовать | ||
16 регистров %xmm0 ... %xmm15. Регистры xmm являются scratch-регистрами, то есть при вызове подпрограмм | ||
сохранение значений не гарантируется (как с регистрами %eax, %ecx, %edx). | ||
|
||
Регистры xmm имеют размер 128 бит и могут хранить 2 64-битных, 4 32-битных целых или вещественных значения, | ||
а также 8 16-битных или 16 8-битных целых значения. Интерпретация битового содержимого регистров xmm | ||
зависит от выполняемой инструкции. | ||
|
||
В стандартном соглашении о вызовах x64 первые 8 параметров вещественных типов float или double передаются | ||
на регистрах %xmm0 ... %xmm7, последующие аргументы передаются в стеке. Результат вещественного типа возвращается | ||
в регистре %xmm0. | ||
|
||
В стандартном соглашении о вызовах x32 аргументы вещественных типов передаются на стеке. Специального выравнивания | ||
для double не требуется. Результат вещественного типа возвращается в регистре FPU %st(0). Даже если результат в %st(0) | ||
не используется вызывающей программой, он должен быть удален из стека FPU. Если в коде x86 для вычислений используется | ||
SSE, а подпрограмма должна вернуть значение вещественного типа, результат из SSE должен быть скопирован на верхушку | ||
стека FPU. | ||
|
||
Например, для копирования значения типа double на FPU может использоваться следующая последовательность операций: | ||
``` | ||
sub $8, %esp // резервируем память | ||
movsd %xmm0, (%esp) // копируем значение double из %xmm0 в стек | ||
fldl (%esp) // загружаем из стека на %st(0) | ||
add $8, %esp // очищаем стек | ||
``` | ||
|
||
## Скалярные вычисления на регистрах SSE | ||
|
||
Регистры SSE можно использовать для обычных вычислений с плавающей точкой. Такие инструкции по терминологии | ||
Intel называются скалярными. В этом случае в регистрах xmm будет использоваться только младшая часть: младшие 32 или 64 бита. | ||
|
||
Для пересылки скалярных значений могут использоваться следующие инструкции: | ||
``` | ||
movsd SRC, DST // пересылка между регистрами xmm и памятью значения double | ||
movss SRC, DST // пересылка значения типа float | ||
``` | ||
Эти инструкции позволяют пересылать значение из регистра xmm в другой регистр xmm, а также между регистрами xmm и памятью. | ||
При обращении к памяти на x86 достаточно, чтобы значение double было выровнено по адресу, кратному 4. | ||
|
||
Со скалярными значениями поддерживаются следующие операции: | ||
``` | ||
addsd SRC, DST // DST += SRC, double | ||
addss SRC, DST // DST += SRC, float | ||
subsd SRC, DST // DST -= SRC, double | ||
subss SRC, DST // DST -= SRC, float | ||
mulsd SRC, DST // DST *= SRC, double | ||
mulss SRC, DST // DST *= SRC, float | ||
divsd SRC, DST // DST /= SRC, double | ||
divss SRC, DST // DST /= SRC, float | ||
sqrtsd SRC, DST // DST = sqrt(SRC), double | ||
sqrtss SRC, DST // DST = sqrt(SRC), float | ||
maxsd SRC, DST // DST = max(SRC, DST), double | ||
maxss SRC, DST // DST = max(SRC, DST), float | ||
minsd SRC, DST // DST = min(SRC, DST), double | ||
minss SRC, DST // DST = min(SRC, DST), float | ||
``` | ||
|
||
Преобразование double->int выполняется инструкцией | ||
``` | ||
cvtsd2si SRC, DST // DST = (int32_t) SRC | ||
``` | ||
Здесь SRC - регистр xmm или память, DST - 32-битный регистр общего назначения. | ||
Инструкция выполняет преобразование вещественног числа типа double в 32-битное знаковое целое число. | ||
|
||
Преобразование double->float выполняется инструкцией: | ||
``` | ||
cvtsd2ss SRC, DST // DST = (float) SRC | ||
``` | ||
|
||
Преобразование int->double выполняется инструкцией: | ||
``` | ||
cvtsi2sd SRC, DST // DST должен быть регистр xmm, SRC либо GPR, либо память | ||
``` | ||
|
||
Преобразование float->double: | ||
``` | ||
cvtss2sd SRC, DST // DST = (double) SRC | ||
``` | ||
|
||
Для преобразований float->int и int->float предназначены инструкции cvtss2si и cvtsi2ss. | ||
|
||
Сравнение двух скалярных значений типа float или double выполняется инструкцией: | ||
``` | ||
comisd SRC, DST // DST - SRC, double | ||
comiss SRC, DST // DST - SRC, float | ||
``` | ||
В результате выполнения операции сравнения устанавливаются флаги PF, CF, ZF. Флаг PF устанавливается, | ||
если результат - неупорядочен. Флаг ZF устанавливается, если значения равны. | ||
Флаг CF устанавливается, если DST < SRC. Для условного перехода после сравнения можно | ||
использовать условные переходы для беззнаковых чисел. Например, ja будет выполнять условный переход, | ||
если DST > SRC. | ||
|
||
## Векторные вычисления на регистрах SSE | ||
|
||
Векторные вычисления в терминологии Intel описываются как вычисления с упакованными (packed) значениями. | ||
|
||
Для пересылки 128-битных значений между памятью и регистрами xmm и между двумя регистрами xmm | ||
используется инструкция | ||
``` | ||
movapd SRC, DST // DST = SRC | ||
``` | ||
если один из аргументов - память, адрес должен быть выровнен по адресу, кратному 16. | ||
Для пересылки по невыровненным адресам можно использовать инструкцию movupd. | ||
|
||
С векторными значениями поддерживаются следующие операции, которые выполняются одновременно со всеми значениями | ||
в регистрах (2 для double или 4 для float): | ||
``` | ||
addpd SRC, DST // DST += SRC, double | ||
addps SRC, DST // DST += SRC, float | ||
subpd SRC, DST // DST -= SRC, double | ||
subps SRC, DST // DST -= SRC, float | ||
mulpd SRC, DST // DST *= SRC, double | ||
mulps SRC, DST // DST *= SRC, float | ||
divpd SRC, DST // DST /= SRC, double | ||
divps SRC, DST // DST /= SRC, float | ||
sqrtpd SRC, DST // DST = sqrt(SRC), double | ||
sqrtps SRC, DST // DST = sqrt(SRC), float | ||
maxpd SRC, DST // DST = max(SRC, DST), double | ||
maxps SRC, DST // DST = max(SRC, DST), float | ||
minpd SRC, DST // DST = min(SRC, DST), double | ||
minps SRC, DST // DST = min(SRC, DST), float | ||
``` | ||
|
||
## Горизонтальные операции | ||
|
||
Обычная операция над упакованными SSE-регистрами может рассматриваться как "вертикальная". Например, | ||
рассмотрим инструкцию `ADDPS A, B`. Эта инструкция складывает четыре float-значения в операнде A | ||
с соответствующими 4 значениями в операнде B и кладет результат в операнд B. Если A и B рассматривать | ||
как массивы из 4 значений типа float, то операция может быть описана следующим образом: | ||
|
||
``` | ||
float A[4]; | ||
float B[4]; | ||
B[0] = A[0] + B[0] | ||
B[1] = A[1] + B[1] | ||
B[2] = A[2] + B[2] | ||
B[3] = A[3] + B[3] | ||
``` | ||
|
||
В противовес "вертикальной" операции "горизонтальная" операция вовлекает соседние значение в одном регистре. | ||
Например, инструкция `HADDPS A, B` выполняется следующим образом: | ||
|
||
``` | ||
float A[4]; | ||
float B[4]; | ||
B[0] = B[0] + B[1]; | ||
B[1] = B[2] + B[3]; | ||
B[2] = A[0] + A[1]; | ||
B[3] = A[2] + A[3]; | ||
``` | ||
|
||
## Ссылки | ||
|
||
1. [x86 Instruction Set Reference](http://x86.renejeschke.de/) |