Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Traducciones**

* [English](./README.md)
* [Français](./README.fr.md)
* [Français](./README.fr.md)
* [Русский](./README.ru.md)
3 changes: 2 additions & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Traductions**

* [English](./README.md)
* [Spanish](./README.es.md)
* [Español](./README.es.md)
* [Русский](./README.ru.md)
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB
**Translations**

* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Español](./README.es.md)
* [Русский](./README.ru.md)
19 changes: 19 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Добро пожаловать в Школу языка ассемблера FFmpeg. Вы сделали первый шаг на самом интересном, сложном и полезном пути в программировании. Эти уроки дадут вам основу в том, как язык ассемблера используется в FFmpeg, и откроют ваши глаза на то, что на самом деле происходит в вашем компьютере.

**Требуемые знания**

* Знание языка C, в частности указателей. Если вы не знаете C, проработайте книгу [Язык программирования C](https://en.wikipedia.org/wiki/The_C_Programming_Language)
* Школьная математика (скаляр против вектора, сложение, умножение и т.д.)

**Уроки**

В этом Git-репозитории есть уроки и задания (пока не загружены), которые соответствуют каждому уроку. К концу уроков вы сможете вносить вклад в FFmpeg.

Доступен сервер Discord для ответов на вопросы:
https://discord.com/invite/Ks5MhUhqfB

**Переводы**

* [English](./README.md)
* [Français](./README.fr.md)
* [Español](./README.es.md)
217 changes: 217 additions & 0 deletions lesson_01/index.ru.md

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions lesson_02/index.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
**Урок второй по языку ассемблера FFmpeg**

Теперь, когда вы написали свою первую функцию на языке ассемблера, мы введем ветвления и циклы.

Нам нужно сначала ввести идею меток и переходов. В искусственном примере ниже инструкция jmp перемещает инструкцию кода после ".loop:". ".loop:" известен как *метка*, где точка, предшествующая метке, означает, что это *локальная метка*, эффективно позволяющая вам повторно использовать одно и то же имя метки в нескольких функциях. Этот пример, конечно, показывает бесконечный цикл, но позже мы расширим его до чего-то более реалистичного.

```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```

Прежде чем создать реалистичный цикл, мы должны ввести регистр *FLAGS*. Мы не будем углубляться в тонкости *FLAGS* слишком сильно (опять же, потому что операции GPR в значительной степени являются строительными лесами), но есть несколько флагов, таких как Zero-Flag, Sign-Flag и Overflow-Flag, которые устанавливаются на основе вывода большинства не-mov инструкций на скалярных данных, таких как арифметические операции и сдвиги.

Вот пример, где счетчик цикла считает вниз до нуля, и jg (переход, если больше нуля) — это условие цикла. dec r0q устанавливает FLAGS на основе значения r0q после инструкции, и вы можете переходить на основе них.

```assembly
mov r0q, 3
.loop:
; сделать что-то
dec r0q
jg .loop ; перейти, если больше нуля
```

Это эквивалентно следующему коду C:

```c
int i = 3;
do
{
// сделать что-то
i--;
} while(i > 0)
```

Этот код C немного неестественен. Обычно цикл в C пишется так:

```c
int i;
for(i = 0; i < 3; i++) {
// сделать что-то
}
```

Это примерно эквивалентно (нет простого способа соответствовать этому циклу ```for```):

```assembly
xor r0q, r0q
.loop:
; сделать что-то
inc r0q
cmp r0q, 3
jl .loop ; перейти, если (r0q - 3) < 0, т.е. (r0q < 3)
```

Есть несколько вещей, на которые следует обратить внимание в этом фрагменте. Во-первых, это ```xor r0q, r0q```, который является обычным способом установки регистра в ноль, что на некоторых системах быстрее, чем ```mov r0q, 0```, потому что, проще говоря, фактической загрузки не происходит. Он также может быть использован на регистрах SIMD с ```pxor m0, m0``` для обнуления всего регистра. Следующее, что следует отметить, — это использование cmp. cmp эффективно вычитает второй регистр из первого (без сохранения значения где-либо) и устанавливает *FLAGS*, но, как указано в комментарии, его можно читать вместе с переходом (jl = перейти, если меньше нуля), чтобы перейти, если ```r0q < 3```.

Обратите внимание, как в этом фрагменте есть одна дополнительная инструкция (cmp). Вообще говоря, меньше инструкций означает более быстрый код, поэтому предпочтителен более ранний фрагмент. Как вы увидите в будущих уроках, есть больше трюков, используемых для избежания этой дополнительной инструкции и установки *FLAGS* арифметической или другой операцией. Обратите внимание, как мы не пишем ассемблер для точного соответствия циклам C, мы пишем циклы, чтобы сделать их как можно быстрее в ассемблере.

Вот некоторые распространенные мнемоники перехода, которые вы в конечном итоге будете использовать (*FLAGS* там для полноты, но вам не нужно знать специфику для написания циклов):

| Мнемоника | Описание | FLAGS |
| :---- | :---- | :---- |
| JE/JZ | Перейти, если равно/ноль | ZF = 1 |
| JNE/JNZ | Перейти, если не равно/не ноль | ZF = 0 |
| JG/JNLE | Перейти, если больше/не меньше или равно (со знаком) | ZF = 0 and SF = OF |
| JGE/JNL | Перейти, если больше или равно/не меньше (со знаком) | SF = OF |
| JL/JNGE | Перейти, если меньше/не больше или равно (со знаком) | SF ≠ OF |
| JLE/JNG | Перейти, если меньше или равно/не больше (со знаком) | ZF = 1 or SF ≠ OF |

**Константы**

Давайте посмотрим на несколько примеров, показывающих, как использовать константы:

```assembly
SECTION_RODATA

constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```

* SECTION_RODATA указывает, что это раздел данных только для чтения. (Это макрос, потому что различные выходные форматы файлов, которые используют операционные системы, объявляют это по-разному)
* constants_1: Метка constants_1 определена как ```db``` (объявить байт) - т.е. эквивалентно uint8_t constants_1[4] = {1, 2, 3, 4};
* constants_2: Это использует макрос ```times 2``` для повторения объявленных слов - т.е. эквивалентно uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1};

Эти метки, которые ассемблер преобразует в адрес памяти, затем могут использоваться в загрузках (но не в сохранениях, так как они только для чтения). Некоторые инструкции принимают адрес памяти в качестве операнда, поэтому они могут использоваться без явных загрузок в регистр (есть плюсы и минусы этого).

**Смещения**

Смещения — это расстояние (в байтах) между последовательными элементами в памяти. Смещение определяется **размером каждого элемента** в структуре данных.

Теперь, когда мы можем писать циклы, пришло время получать данные. Но есть некоторые отличия по сравнению с C. Давайте посмотрим на следующий цикл в C:

```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```

4-байтовое смещение между элементами data предварительно рассчитывается компилятором C. Но при написании ассемблера вручную вам нужно рассчитывать эти смещения самостоятельно.

Давайте посмотрим на синтаксис для вычислений адресов памяти. Это применяется ко всем типам адресов памяти:

```assembly
[base + scale*index + disp]
```

* base - Это GPR (обычно указатель из аргумента функции C)
* scale - Это может быть 1, 2, 4, 8. 1 по умолчанию
* index - Это GPR (обычно счетчик цикла)
* disp - Это целое число (до 32-бит). Смещение — это смещение в данных

x86asm предоставляет константу mmsize, которая позволяет узнать размер регистра SIMD, с которым вы работаете.

Вот простой (и бессмысленный) пример для иллюстрации загрузки из пользовательских смещений:

```assembly
;static void simple_loop(const uint8_t *src)
INIT_XMM sse2
cglobal simple_loop, 1, 2, 2, src
movq r1q, 3
.loop:
movu m0, [srcq]
movu m1, [srcq+2*r1q+3+mmsize]

; сделать что-то

add srcq, mmsize
dec r1q
jg .loop

RET
```

Обратите внимание, как в ```movu m1, [srcq+2*r1q+3+mmsize]``` ассемблер предварительно рассчитает правильную константу смещения для использования. На следующем уроке мы покажем вам трюк, чтобы избежать необходимости делать add и dec в цикле, заменив их одним add.

**LEA**

Теперь, когда вы понимаете смещения, вы можете использовать lea (Load Effective Address, загрузить эффективный адрес). Это позволяет вам выполнять умножение и сложение с помощью одной инструкции, что будет быстрее, чем использование нескольких инструкций. Конечно, есть ограничения на то, на что вы можете умножать и что добавлять, но это не мешает lea быть мощной инструкцией.

```assembly
lea r0q, [base + scale*index + disp]
```

Вопреки названию, LEA может использоваться как для обычной арифметики, так и для вычислений адресов. Вы можете сделать что-то настолько сложное, как:

```assembly
lea r0q, [r1q + 8*r2q + 5]
```

Обратите внимание, что это не влияет на содержимое r1q и r2q. Это также не влияет на *FLAGS* (поэтому вы не можете переходить на основе вывода). Использование LEA избегает всех этих инструкций и временных регистров (этот код не эквивалентен, потому что add изменяет *FLAGS*):

```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; сдвиг арифметический влево 3 = * 8
add r3q, 5
add r0q, r3q
```

Вы увидите lea, используемый много для настройки адресов перед циклами или выполнения вычислений, подобных приведенным выше. Обратите внимание, конечно, что вы не можете делать все типы умножения и сложения, но умножения на 1, 2, 4, 8 и сложение фиксированного смещения распространены.

В задании вам нужно будет загрузить константу и добавить значения к вектору SIMD в цикле.

[Следующий урок](../lesson_03/index.ru.md)
Loading