Skip to content

Commit

Permalink
sem10 added
Browse files Browse the repository at this point in the history
  • Loading branch information
blackav committed Jan 24, 2020
1 parent 8e20d69 commit 15622c9
Showing 1 changed file with 164 additions and 0 deletions.
164 changes: 164 additions & 0 deletions 10-asm3/README.md
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/)

0 comments on commit 15622c9

Please sign in to comment.