Repository: FFmpeg/asm-lessons
Branch: main
Commit: 171e7261abcd
Files: 20
Total size: 184.2 KB
Directory structure:
gitextract_u2mmhjd8/
├── README.es.md
├── README.fr.md
├── README.md
├── README.tr.md
├── README.zh.md
├── lesson_01/
│ ├── index.es.md
│ ├── index.fr.md
│ ├── index.md
│ ├── index.tr.md
│ └── index.zh.md
├── lesson_02/
│ ├── index.es.md
│ ├── index.fr.md
│ ├── index.md
│ ├── index.tr.md
│ └── index.zh.md
└── lesson_03/
├── index.es.md
├── index.fr.md
├── index.md
├── index.tr.md
└── index.zh.md
================================================
FILE CONTENTS
================================================
================================================
FILE: README.es.md
================================================
Bienvenido a la Escuela de Lenguaje Ensamblador de FFmpeg. Has dado el primer paso en el viaje más interesante, desafiante y gratificante de la programación. Estas lecciones te proporcionarán una base sobre cómo se utiliza el ensamblador en FFmpeg y te abrirán los ojos a lo que realmente ocurre en tu ordenador.
**Conocimientos necesarios**
* Conocimientos de C, en particular de punteros. Si no sabes C, estudia el libro [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language)
* Matemáticas de secundaria (escalares vs vectores, suma, multiplicación, etc.)
**Lecciones**
En este repositorio Git hay lecciones y tareas (aún no subidas) que corresponden a cada lección. Al final de las lecciones, podrás contribuir a FFmpeg.
Hay un servidor Discord disponible para responder preguntas:
https://discord.com/invite/Ks5MhUhqfB
**Traducciones**
* [English](./README.md)
* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Turkish](./README.tr.md)
* [中文](./README.zh.md)
================================================
FILE: README.fr.md
================================================
Bienvenue dans la FFmpeg School of Assembly Language. Vous avez fait le premier pas dans le voyage le plus intéressant, le plus stimulant et le plus gratifiant de la programmation. Ces leçons vous donneront des bases sr la façon sur la manière l'assembleur est utilisée dans FFmpeg et vous ouvriront les yeux sur ce qui se passe réellement à l'intérieur de votre ordinateur...
**Connaissances requises**
* Connaissance en C, particulièrement les pointeurs. Si le C ne vous est pas familier, vous trouverez toutes les bases dans le livre [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language).
* Mathématiques de lycée (scalaire vs vecteur, addition, multiplication, etc...)
**Leçons**
Dans ce répo Git se trouvent les leçons et des exercices (non inclus actuellement) relatifs à chaque leçon. A la fin de l'ensemble des leçons, vous serez capable de contribuer à FFmpeg.
Un serveur discord est accessible pour vous apporter des réponses :
https://discord.com/invite/Ks5MhUhqfB
**Traductions**
* [English](./README.md)
* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Turkish](./README.tr.md)
* [中文](./README.zh.md)
================================================
FILE: README.md
================================================
Welcome to the FFmpeg School of Assembly Language. You have taken the first step on the most interesting, challenging, and rewarding journey in programming. These lessons will give you a grounding in the way assembly language is written in FFmpeg and open your eyes to what's actually going on in your computer.
**Required Knowledge**
* Knowledge of C, in particular pointers. If you don't know C, work through [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) book
* High School Mathematics (scalar vs vector, addition, multiplication etc)
**Lessons**
In this Git repository there are lessons and assignments (not uploaded yet) that correspond with each lessons. By the end of the lessons you'll be able to contribute to FFmpeg.
A discord server is available to answer questions:
https://discord.com/invite/Ks5MhUhqfB
**Translations**
* [English](./README.md)
* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Turkish](./README.tr.md)
* [中文](./README.zh.md)
================================================
FILE: README.tr.md
================================================
FFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, zorlu ve ödüllendirici yolculuğunda ilk adımı attınız. Bu dersler, assembly dilinin FFmpeg'de nasıl yazıldığı konusunda size temel bilgi verecek ve bilgisayarınızda gerçekte neler olup bittiğine gözlerinizi açacaktır.
**Gerekli Bilgiler**
* C bilgisi, özellikle pointer'lar. Eğer C bilmiyorsanız, [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabını çalışın
* Lise Matematiği (skalar vs vektör, toplama, çarpma vb.)
**Dersler**
Bu Git deposunda her derse karşılık gelen dersler ve ödevler (henüz yüklenmedi) bulunmaktadır. Derslerin sonunda FFmpeg'e katkıda bulunabileceksiniz.
Soruları yanıtlamak için bir discord sunucusu mevcuttur:
https://discord.com/invite/Ks5MhUhqfB
**Çeviriler**
* [English](./README.md)
* [Français](./README.fr.md)
* [Spanish](./README.es.md)
* [Turkish](./README.tr.md)
* [中文](./README.zh.md)
================================================
FILE: README.zh.md
================================================
欢迎来到 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)
* [Spanish](./README.es.md)
* [Turkish](./README.tr.md)
* [中文](./README.zh.md)
================================================
FILE: lesson_01/index.es.md
================================================
**Lección Uno de Lenguaje Ensamblador FFmpeg**
**Introducción**
Bienvenido a la Escuela de Lenguaje Ensamblador de FFmpeg. Has dado el primer paso en el viaje más interesante, desafiante y gratificante de la programación. Estas lecciones te proporcionarán una base sobre cómo se utiliza el ensamblador en FFmpeg y te abrirán los ojos a lo que realmente ocurre en tu ordenador.
**Conocimientos necesarios**
* Conocimientos de C, en particular de punteros. Si no sabes C, estudia el libro [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language)
* Matemáticas de secundaria (escalares vs vectores, suma, multiplicación, etc.)
**¿Qué es el lenguaje ensamblador?**
El lenguaje ensamblador es un lenguaje de programación en el que se escribe código que se corresponde directamente con las instrucciones que procesa una CPU. El lenguaje ensamblador legible por humanos, como su nombre indica, se *ensamble* en datos binarios, conocidos como *código máquina*, que la CPU puede entender. Es posible que veas el código en lenguaje ensamblador denominado como *ensamblador* o *asm* para abreviar.
La gran mayoría del código ensamblador en FFmpeg es lo que se conoce como *SIMD, Single Instruction Multiple Data (instrucción única, datos múltiples)*. SIMD se conoce a veces como programación vectorial. Esto significa que una instrucción concreta opera sobre varios elementos de datos al mismo tiempo. La mayoría de los lenguajes de programación operan sobre un elemento de datos a la vez, lo que se conoce como programación secuencial.
Como habrás adivinado, SIMD se presta bien al procesamiento de imágenes, vídeo y audio, que tienen muchos datos ordenados secuencialmente en la memoria. Hay instrucciones especializadas disponibles en la CPU para ayudarnos a procesar datos secuenciales.
En FFmpeg, verás los términos «función de ensamblador», «SIMD» y «vectorizar» utilizados indistintamente. Todos ellos se refieren a lo mismo: escribir una función en lenguaje ensamblador a mano para procesar múltiples elementos de datos de una sola vez. Algunos proyectos también pueden referirse a ellos como «núcleos de ensamblador».
Todo esto puede parecer complicado, pero es importante recordar que en FFmpeg, los estudiantes de secundaria han escrito código ensamblador. Como con todo, el aprendizaje es un 50 % jerga y un 50 % aprendizaje real.
**¿Por qué escribimos en lenguaje ensamblador?**
Para acelerar el procesamiento multimedia. Es muy habitual conseguir una mejora de velocidad de 10 veces o más al escribir código ensamblador, lo que resulta especialmente importante cuando se desea reproducir vídeos en tiempo real sin interrupciones. Además, ahorra energía y prolonga la duración de la batería. Cabe señalar que las funciones de codificación y descodificación de vídeo son algunas de las más utilizadas en el mundo, tanto por los usuarios finales como por las grandes empresas en sus centros de datos. Por lo tanto, incluso una pequeña mejora se acumula rápidamente.
A menudo se ve en Internet que la gente utiliza *intrínsecos*, que son funciones similares a las de C que se asignan a instrucciones de ensamblador para permitir un desarrollo más rápido. En FFmpeg no utilizamos intrínsecos, sino que escribimos el código ensamblador a mano. Se trata de un tema controvertido, pero los intrínsecos suelen ser entre un 10 y un 15 % más lentos que el ensamblador escrito a mano (los defensores de los intrínsecos no estarían de acuerdo), dependiendo del compilador. Para FFmpeg, cada bit de rendimiento adicional ayuda, por lo que escribimos directamente en código ensamblador. También se argumenta que las intrínsecas son difíciles de leer debido al uso de la «[notación húngara] (https://en.wikipedia.org/wiki/Hungarian_notation)».
También es posible que veas *ensamblador en línea* (es decir, sin usar intrínsecos) en algunos lugares de FFmpeg por razones históricas, o en proyectos como el kernel de Linux debido a casos de uso muy específicos. Aquí es donde el código ensamblador no se encuentra en un archivo separado, sino que se escribe en línea con el código C. La opinión predominante en proyectos como FFmpeg es que este código es difícil de leer, no es ampliamente compatible con los compiladores y es imposible de mantener.
Por último, verás a muchos autoproclamados expertos en Internet diciendo que nada de esto es necesario y que el compilador puede hacer toda esta «vectorización» por ti. Al menos con fines de aprendizaje, ignóralos: pruebas recientes en, por ejemplo, [el proyecto dav1d](https://www.videolan.org/projects/dav1d.html) mostraron una aceleración de aproximadamente 2 veces gracias a esta vectorización automática, mientras que las versiones escritas a mano podían alcanzar 8 veces.
**Variantes del lenguaje ensamblador**
Estas lecciones se centrarán en el lenguaje ensamblador x86 de 64 bits. También se conoce como amd64, aunque sigue funcionando en CPU Intel. Existen otros tipos de ensamblador para otras CPU, como ARM y RISC-V, y es posible que en el futuro estas lecciones se amplíen para abarcarlos.
Hay dos tipos de sintaxis de ensamblador x86 que verás en Internet: AT&T e Intel. La sintaxis AT&T es más antigua y más difícil de leer en comparación con la sintaxis Intel. Por lo tanto, utilizaremos la sintaxis Intel.
**Materiales de apoyo**
Quizás te sorprenda saber que los libros o recursos en línea como Stack Overflow no son especialmente útiles como referencias. Esto se debe en parte a nuestra decisión de utilizar ensamblador escrito a mano con sintaxis Intel. Pero también a que muchos recursos en línea se centran en la programación de sistemas operativos o hardware, y suelen utilizar código no SIMD. El ensamblador FFmpeg se centra especialmente en el procesamiento de imágenes de alto rendimiento y, como verás, se trata de un enfoque particularmente único de la programación en ensamblador. Dicho esto, una vez completadas estas lecciones, te resultará fácil comprender otros casos de uso del ensamblador.
Muchos libros profundizan en numerosos detalles de la arquitectura informática antes de enseñar ensamblador. Esto está bien si es lo que quieres aprender, pero desde nuestro punto de vista, es como estudiar ingeniería antes de aprender a conducir un coche.
Dicho esto, los diagramas de las últimas partes del libro «The Art of 64-bit assembly» que muestran las instrucciones SIMD y su comportamiento de forma visual son útiles: [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/)
Hay un servidor Discord disponible para responder preguntas:
[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB)
**Registros**
Los registros son áreas de la CPU donde se pueden procesar los datos. Las CPU no operan directamente sobre la memoria, sino que los datos se cargan en los registros, se procesan y se vuelven a escribir en la memoria. En lenguaje ensamblador, por lo general, no se pueden copiar datos directamente de una ubicación de memoria a otra sin pasar primero esos datos por un registro.
**Registros de propósito general**
El primer tipo de registro es el conocido como registro de propósito general (GPR). Los GPR se denominan de propósito general porque pueden contener datos, en este caso hasta un valor de 64 bits, o una dirección de memoria (un puntero). Un valor en un GPR se puede procesar mediante operaciones como suma, multiplicación, desplazamiento, etc.
En la mayoría de los libros sobre ensamblador, hay capítulos enteros dedicados a las sutilezas de los GPR, sus antecedentes históricos, etc. Esto se debe a que los GPR son importantes cuando se trata de programación de sistemas operativos, ingeniería inversa, etc. En el código ensamblador escrito en FFmpeg, los GPR son más bien como andamios y, en la mayoría de los casos, su complejidad no es necesaria y se abstrae.
**Registros vectoriales**
Los registros vectoriales (SIMD), como su nombre indica, contienen múltiples elementos de datos. Existen varios tipos de registros vectoriales:
* Registros mm: registros MMX, de 64 bits, históricos y que ya no se utilizan mucho.
* Registros xmm: registros XMM, de 128 bits, ampliamente disponibles.
* Registros ymm: registros YMM, de 256 bits, con algunas complicaciones a la hora de utilizarlos.
* Registros zmm: registros ZMM, de 512 bits, disponibilidad limitada
La mayoría de los cálculos en la compresión y descompresión de vídeo se basan en números enteros, por lo que nos ceñiremos a eso. Aquí hay un ejemplo de 16 bytes en un registro xmm:
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Pero podrían ser ocho palabras (enteros de 16 bits).
| a | b | c | d | e | f | g | h |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
O cuatro palabras dobles (enteros de 32 bits)
| a | b | c | d |
| :---- | :---- | :---- | :---- |
O dos palabras cuádruples (enteros de 64 bits):
| a | b |
| :---- | :---- |
En resumen:
* **b**ytes: datos de 8 bits
* **w**ords: datos de 16 bits
* **d**oublewords: datos de 32 bits
* **q**uadwords: datos de 64 bits
* **d**ouble **q**uadwords: datos de 128 bits
Los caracteres en negrita serán importantes más adelante.
**x86inc.asm include**
Verás que en muchos ejemplos incluimos el archivo x86inc.asm. X86inc.asm es una capa de abstracción ligera que se utiliza en FFmpeg, x264 y dav1d para facilitar el trabajo de los programadores de ensamblador. Ayuda de muchas maneras, pero, para empezar, una de las cosas útiles que hace es etiquetar los GPR, r0, r1, r2. Esto significa que no tienes que recordar ningún nombre de registro. Como se ha mencionado anteriormente, los GPR son generalmente solo andamios, por lo que esto facilita mucho las cosas.
**Un fragmento sencillo de código ensamblador escalar**
Veamos un fragmento sencillo (y muy artificial) de código ensamblador escalar (código ensamblador que opera sobre elementos de datos individuales, uno a uno, dentro de cada instrucción) para ver qué está pasando:
```assembly
mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
```
En la primera línea, el *valor inmediato* 3 (un valor almacenado directamente en el propio código ensamblador, en contraposición a un valor obtenido de la memoria) se almacena en el registro r0 como una palabra cuádruple. Ten en cuenta que, en la sintaxis de Intel, el operando de origen (el valor o la ubicación que proporciona los datos, situado a la derecha) se transfiere al operando de destino (la ubicación que recibe los datos, situada a la izquierda), de forma muy similar al comportamiento de memcpy. También puedes leerlo como «r0q = 3», ya que el orden es el mismo. El sufijo «q» de r0 designa que el registro se utiliza como una palabra cuádruple. inc incrementa el valor para que r0q contenga 4, dec decrementa el valor de nuevo a 3. imul multiplica el valor por 5. Así, al final, r0q contiene 15.
Ten en cuenta que las instrucciones legibles para los humanos, como mov e inc, que el ensamblador convierte en código máquina, se conocen como *mnemónicos*. Es posible que veas en Internet y en libros mnemónicos representados con letras mayúsculas, como MOV e INC, pero son iguales que las versiones en minúsculas. En FFmpeg, utilizamos mnemónicos en minúsculas y reservamos las mayúsculas para las macros.
**Comprender una función vectorial básica**
Esta es nuestra primera función SIMD:
```assembly
%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
```
Repasemos línea por línea:
```assembly
%include "x86inc.asm"
```
Se trata de un «encabezado» o *header* desarrollado en las comunidades x264, FFmpeg y dav1d para proporcionar *helpers*, nombres predefinidos y macros (como cglobal más abajo) que simplifican la escritura de ensamblador.
```assembly
SECTION .text
```
Esto indica la sección donde se coloca el código que se desea ejecutar. Esto contrasta con la sección .data, donde se pueden colocar datos constantes.
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
```
La primera línea es un comentario (el punto y coma «;» en asm es como «//» en C) que muestra cómo es el argumento de la función en C. La segunda línea muestra cómo inicializamos la función para utilizar los registros XMM, utilizando el conjunto de instrucciones sse2. Esto se debe a que paddb es una instrucción sse2. Trataremos sse2 con más detalle en la próxima lección.
```assembly
cglobal add_values, 2, 2, 2, src, src2
```
Esta es una línea importante, ya que define una función C llamada «add_values».
Repasemos cada elemento uno por uno:
* El primer parámetro muestra que la función tiene dos argumentos.
* El parámetro siguiente muestra que utilizaremos dos GPR para los argumentos. En algunos casos, es posible que queramos utilizar más GPR, por lo que debemos indicar a x86util que necesitamos más.
* El parámetro siguiente le indica a x86util cuántos registros XMM vamos a utilizar.
* Los dos parámetros siguientes son etiquetas para los argumentos de la función.
Cabe señalar que es posible que el código más antiguo no tenga etiquetas para los argumentos de la función, sino que se dirija directamente a los GPR utilizando r0, r1, etc.
```assembly
movu m0, [srcq]
movu m1, [src2q]
```
movu es la abreviatura de movdqu (move double quad unaligned). La alineación se tratará en otra lección, pero por ahora movu puede considerarse como un movimiento de 128 bits desde [srcq]. En el caso de mov, los corchetes significan que la dirección en [srcq] se está desreferenciando, lo equivalente a **src en C.* Esto es lo que se conoce como una carga. Ten en cuenta que el sufijo «q» se refiere al tamaño del puntero * (*es decir, en C representa *sizeof(*src) == 8 en sistemas de 64 bits, y x86asm es lo suficientemente inteligente como para usar 32 bits en sistemas de 32 bits), pero la carga subyacente es de 128 bits.
Tenga en cuenta que no nos referimos a los registros vectoriales por su nombre completo, en este caso xmm0, sino como m0, una forma abstracta. En futuras lecciones verá cómo esto significa que puede escribir código una vez y hacer que funcione en múltiples tamaños de registros SIMD.
```assembly
paddb m0, m1
```
paddb (léase en voz alta como *p-add-b*) suma cada byte de cada registro, tal y como se muestra a continuación. El prefijo «p» significa «empaquetado» o «packed» y se utiliza para identificar las instrucciones vectoriales frente a las instrucciones escalares. El sufijo «b» indica que se trata de una suma por bytes (suma de bytes).
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\+
| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\=
| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
```assembly
movu [srcq], m0
```
Esto es lo que se conoce como «store». Los datos se vuelven a escribir en la dirección del puntero srcq.
```assembly
RET
```
Esta es una macro para indicar los retornos de la función. Prácticamente todas las funciones de ensamblador en FFmpeg modifican los datos de los argumentos en lugar de devolver un valor.
Como verás en la tarea, creamos punteros de función a funciones de ensamblador y los utilizamos cuando están disponibles.
[Siguiente lección](../lesson_02/index.es.md)
================================================
FILE: lesson_01/index.fr.md
================================================
**FFmpeg Assembly Language Leçon Une**
**Introduction**
Bienvenue dans la FFmpeg School of Assembly Language. Vous avez fait le premier pas dans le voyage le plus intéressant, le plus stimulant et le plus gratifiant de la programmation. Ces leçons vous donneront des bases sur la façon dont l’assembleur est utilisé dans FFmpeg et vous ouvriront les yeux sur ce qui se passe réellement dans votre ordinateur...
**Connaissances requises**
* Connaissance en C, particulièrement les pointeurs. Si le C ne vous est pas familier, vous trouverez toutes les bases dans le livre [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language).
* Mathématiques de lycée (scalaire vs vecteur, addition, multiplication, etc...)
**Qu'est-ce que l'assembleur ?**
L'assembleur est un langage de programmation où le code que vous écrivez correspond directement à des instructions compréhensibles par un CPU. L'assembleur lisible par l'humain est, comme son nom l'indique, *assemblé* en données binaires, connues sous le nom de *langage machine*, que le CPU peut comprendre. Le code assembleur est souvent appelé "assembleur" ou "asm" en abrégé.
La grande majorité de l'assembleur de FFmpeg est ce que l'on appelle *SIMD, Single Instruction Multiple Data (instruction unique, données multiples)*. SIMD est parfois désigné sous le terme de programmation vectorielle. Cela signifie qu'une instruction particulière opère sur plusieurs éléments de données simultanément. La plupart des langages de programmation traitent un seul élément de données à la fois, ce que l'on appelle la programmation scalaire.
Comme vous l'avez peut-être deviné, SIMD se prête bien au traitement d'images, de vidéos et d'audio, qui contiennent une grande quantité de données organisées séquentiellement en mémoire. Des instructions spécialisées dans le processeur nous aideront à traiter ces données séquentielles.
Dans FFmpeg, vous verrez que les termes `fonction en assembleur`, `SIMD`, `vectorisation` sont utilisés de manière interchangeable. Ils désignent tous la même chose : écrire une fonction en assembleur à la main pour traiter plusieurs éléments de données en une seule fois. Certains projets peuvent aussi faire référence à des `noyaux en assembleur`.
Tout cela peut sembler compliqué, mais il est important de rappeler que des lycéens ont écrit de l'assembleur dans FFmpeg. Comme partout, l'apprentissage, c'est 50 % du jargon et 50 % d'apprentissage réel.
**Pourquoi écrivons-nous en assembleur ?**
Pour rendre le traitement multimédia rapide. Il est très courant d'avoir une vitesse de traitement au moins 10 fois plus rapide en écrivant en assembleur, ce qui est d'autant plus important quand nous voulons lire des vidéos en temps réel sans saccade. Cela permet aussi d'économiser de l'énergie et d'étendre la durée de vie des batteries. Il est important de souligner que les fonctions d'encodage et de décodage sont parmi les fonctions les plus utilisées au monde, aussi bien par les utilisateurs finaux que par les multinationales dans leurs data centers. Donc, même une petite amélioration apporte beaucoup.
Vous verrez souvent, en ligne, des gens utiliser des *fonctions intrinsèques*, des fonctions ressemblant à du C mais qui sont en fait des instructions en assembleur utilisées pour permettre un développement plus rapide. Dans FFmpeg, nous n'utilisons pas ce genre de fonctions, nous écrivons tout le code en assembleur à la main. C'est un point de discorde, mais les fonctions intrinsèques sont environ 10 à 15 % plus lentes que l'équivalent en assembleur écrit à la main (les partisans de ces fonctions ne seraient pas d'accord), tout dépend du compilateur. Pour FFmpeg, chaque amélioration compte, c'est pourquoi nous écrivons tout le code directement en assembleur. Un argument en notre faveur est l'utilisation de la [notation hongroise](https://fr.wikipedia.org/wiki/Notation_hongroise) dans les fonctions intrinsèques, qui complique leur lecture.
Aussi, vous verrez des références à de l'*assembleur en ligne* (ou *assembleur inline*), c'est-à-dire n'utilisant pas de fonctions intrinsèques, à quelques endroits dans FFmpeg pour des raisons historiques, ou dans des projets comme le noyau Linux dans des scénarios d'utilisation très spécifiques. Ici, le code assembleur n'est pas dans un fichier séparé mais écrit directement dans des fichiers avec du code en C. Le point de vue majoritaire dans des projets comme FFmpeg est que ce code est difficile à lire, pas largement supporté par les compilateurs et difficile à maintenir.
Enfin, vous verrez beaucoup d'experts autoproclamés sur Internet disant que rien de tout cela n'est nécessaire et que le compilateur peut effectuer cette `vectorisation` pour vous. Dans une optique d'apprentissage, ignorez-les : des tests récents comme ceux présents sur le [projet dav1d](https://www.videolan.org/projects/dav1d.html) ont montré un gain de vitesse d'environ x2 grâce à cette vectorisation automatique, tandis que pour des versions écrites à la main, ce gain montait à x8.
**Les variétés d'assembleurs**
Ces leçons se concentreront sur l'assembleur x86 64 bits, aussi connu sous le nom d'amd64, bien qu'il fonctionne également sur les CPUs Intel. Il existe autant de types d'assembleurs que de CPUs, comme ceux pour ARM ou RISC-V, avec potentiellement des mises à jour de ces cours pour les inclure.
Il existe deux types de syntaxe pour l'assembleur x86 que vous trouverez en ligne : AT&T et Intel. La première est plus ancienne et plus difficile à lire comparée à la seconde. Nous nous intéresserons à cette dernière.
**Matériels supportés**
Vous serez peut-être surpris d'entendre que des livres ou des ressources en ligne comme Stack Overflow ne sont pas d'une grande aide comme références. Particulièrement à cause de notre choix d'utiliser de l'assembleur écrit à la main avec la syntaxe Intel. Mais aussi parce que beaucoup de ces ressources en ligne se concentrent sur de la programmation de systèmes d'exploitation ou de la programmation pour du hardware, n'utilisant pas du code SIMD. L'assembleur de FFmpeg est majoritairement axé autour du traitement d'images haute performance, et comme vous le verrez, avec une approche particulière de la programmation en assembleur. Ceci étant dit, il est facile de comprendre d'autres cas d'utilisation de l'assembleur une fois ces leçons terminées.
Beaucoup de livres détaillent différentes architectures d'ordinateur avant de détailler l'assembleur. De notre point de vue, c'est très bien si c'est ce que vous voulez apprendre, mais cela revient à vouloir étudier les moteurs avant d'apprendre à conduire une voiture.
Une fois ceci dit, dans les parties suivantes, les diagrammes du livre *The Art of 64-bit Assembly* montrant les instructions SIMD et leur comportement sous forme visuelle vous seront très utiles : [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/)
Un serveur Discord est disponible pour répondre à vos questions :
[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB)
**Les registres**
Les registres sont des zones du CPU où les données peuvent être traitées. Les CPUs n'interviennent pas directement sur la mémoire : les données sont plutôt chargées dans des registres, traitées, puis écrites à nouveau en mémoire. En assembleur, de manière générale, vous ne pouvez pas copier directement les données d'un emplacement mémoire à un autre sans d'abord passer ces données dans un registre.
**Registres à usage général**
Le premier type de registre que nous allons rencontrer est connu sous le nom de registre à usage général (GPR). Les GPR sont appelés ainsi car ils peuvent contenir soit des données, soit une valeur allant jusqu'à 64 bits, soit une adresse mémoire (un pointeur). Une valeur dans un GPR peut être traitée par des opérations telles que l'addition, la multiplication, le décalage, etc...
Dans la plupart des livres sur l'assembleur, de nombreux chapitres entiers sont consacrés aux subtilités des GPR, à leur histoire, etc... Car les GPR ont joué un rôle important dans la programmation de systèmes d'exploitation, le reverse engineering, etc... Dans l'assembleur écrit pour FFmpeg, les GPR sont considérés comme des échafaudages et la plupart du temps, leur complexité n'est pas nécessaire et est abstraite.
**Registres vectoriels**
Les registres vectoriels (SIMD), comme leur nom le suggère, contiennent plusieurs éléments de données. Il existe différents types de registres vectoriels :
* registres `mm` : des registres `MMX`, de taille 64 bits, historiques et peu utilisés de nos jours
* registres `xmm` : des registres `XMM`, de taille 128 bits, largement disponibles
* registres `ymm` : des registres `YMM`, de taille 256 bits, avec quelques complications lors de leur utilisation
* registres `zmm` : des registres `ZMM`, de taille 512 bits, très peu disponibles
La plupart des calculs de compression et de décompression vidéo sont basés sur des entiers, nous nous y limiterons. Voici un exemple d'un registre `XMM` de 16 octets :
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Cela pourrait aussi être huit mots (entiers de 16 bits):
| a | b | c | d | e | f | g | h |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Ou quatre double mots (entiers de 32 bits):
| a | b | c | d |
| :---- | :---- | :---- | :---- |
Ou deux quadruples mots (entiers de 64 bits):
| a | b |
| :---- | :---- |
Pour récapituler :
* **b**ytes - données de 8 bits
* **w**ords - données de 16 bits
* **d**oublewords - données de 32 bits
* **q**uadwords - données de 64 bits
* **d**ouble **q**uadwords - données de 128 bits
Les caractères en gras seront importants dans la suite.
**Header x86inc.asm**
Dans beaucoup d'exemples, nous incluons le fichier x86inc.asm. x86inc.asm est une couche d'abstraction légère utilisée dans FFmpeg, x264 et dav1d pour faciliter la vie des développeurs. Elle aide de nombreuses manières, mais l'une des choses utiles qu'elle permet est, entre autres, d'étiqueter les GPR `r0`, `r1` et `r2`. Vous n'aurez pas à vous souvenir des noms des registres. Comme mentionné précédemment, les GPR sont généralement juste des échafaudages, donc cela rend les choses beaucoup plus simples.
**Un extrait simple d'assembleur scalaire**
Regardons un extrait simple (et totalement artificiel) de code assembleur scalaire (code assembleur qui opère sur des éléments de données individuels, un à la fois, dans chaque instruction) pour voir ce qui se passe :
```assembly
mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
```
À la première ligne, la *valeur immédiate* 3 (une valeur stockée directement dans le code assembleur lui-même, contrairement à une valeur récupérée depuis la mémoire) est stockée dans le registre `r0` sous forme de quadword. Notez qu'en syntaxe Intel, l'opérande source (la valeur ou l'emplacement fournissant les données, situé à droite) est transféré vers l'opérande de destination (l'emplacement recevant les données, situé à gauche), un peu comme le comportement de *memcpy*. Vous pouvez aussi le lire comme `r0q = 3`, puisque l'ordre est le même. Le suffixe `q` de `r0` désigne le registre comme étant utilisé comme quadword. `inc` incrémente la valeur pour que `r0q` contienne 4, `dec` décrémente la valeur pour revenir à 3. `imul` multiplie la valeur par 5. À la fin, `r0q` contient donc 15.
Notez que les instructions lisibles par l'humain, comme `mov` et `inc`, qui sont assemblées en code machine par l’assembleur, sont appelées *mnémoniques*. Vous pouvez voir en ligne ou dans des livres ces mnémoniques représentées avec des lettres majuscules comme `MOV` et `INC`, mais elles sont identiques à leurs versions en minuscules. Dans FFmpeg, nous utilisons les mnémoniques en minuscules et réservons les majuscules pour les macros.
**Comprendre une fonction vectorielle de base**
Voici notre première fonction SIMD :
```assembly
%include "x86inc.asm"
SECTION .text
;static void add_values(const uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
```
Passons en revue le code ligne par ligne :
```assembly
%include "x86inc.asm"
```
Ce `header`, développé dans les communautés x264, FFmpeg et dav1d, fournit des auxiliaires, des noms prédéfinis et des macros (comme `cglobal` ci-dessous) pour simplifier l’écriture du code assembleur.
```assembly
SECTION .text
```
Cela désigne la section où le code que vous voulez exécuter est placé.Cela contraste avec la section `.data`, où vous pouvez placer des données constantes.
```assembly
;static void add_values(const uint8_t *src, const uint8_t *src2);
INIT_XMM sse2
```
La première ligne est un commentaire (le point-virgule `;` en assembleur est l’équivalent du `//` en C) montrant à quoi ressemble le prototype de la fonction en C.La seconde ligne montre comment nous initialisons la fonction pour qu’elle utilise des registres XMM, en employant le jeu d’instructions SSE2.
Cela est nécessaire car l’instruction `paddb` fait partie de SSE2.Nous aborderons SSE2 plus en détail dans la prochaine leçon.
```assembly
cglobal add_values, 2, 2, 2, src, src2
```
Le code précédent montre une ligne importante : la définition d’une fonction C appelée `add_values`.
Passons en revue chaque élément de la ligne :
* Le premier paramètre indique que la fonction prend deux arguments (le premier `2`).
* Le deuxième `2` indique que nous allons utiliser deux GPR pour les arguments. Dans certains cas, nous pourrions vouloir en utiliser davantage, et nous devrons donc indiquer à `x86util` que nous en avons besoin de plus.
* Le paramètre suivant indique à `x86util` combien de registres `XMM` seront utilisés.
* Les deux derniers paramètres sont les labels utilisés pour les arguments de la fonction `add_values`.
Il est important de noter que le code plus ancien peut ne pas avoir de labels comme arguments de fonctions, mais plutôt les adresses des registres GPR à la place, en utilisant `r0`, `r1`, etc.
```assembly
movu m0, [srcq]
movu m1, [src2q]
```
`movu` est une abréviation de `movdqu` (*move double quad unaligned*).L’alignement sera abordé dans une autre leçon, mais pour l’instant, vous pouvez considérer `movu` comme un transfert de 128 bits depuis `[srcq]`.Dans le cas de `mov`, les crochets `[]` signifient que l’adresse contenue dans `[srcq]` est déréférencée, ce qui équivaut à `*src` en C.C’est ce qu’on appelle un chargement (load).
Le suffixe `q` fait référence à la taille du pointeur.En C, cela correspond à `sizeof(*src) == 8` sur les systèmes 64 bits.L’assembleur x86 est suffisamment intelligent pour utiliser 32 bits sur les systèmes 32 bits, mais l’opération de chargement sous-jacente reste de 128 bits.
Remarquez que nous ne faisons pas référence aux registres vectoriels par leur nom complet (dans ce cas `xmm0`), mais plutôt sous une forme abstraite comme `m0`.Dans les prochaines leçons, vous verrez comment cette approche permet d’écrire du code une seule fois et de le faire fonctionner avec plusieurs tailles de registres SIMD.
```assembly
paddb m0, m1
```
`paddb` (lisez ceci *p-add-b*) additionne chaque octet dans chaque registre, comme illustré ci-dessous. Le préfixe `p` signifie `packed` et sert à distinguer les instructions vectorielles des instructions scalaires. Le suffixe `b` indique que l'opération est effectuée au niveau des octets (addition d’octets).
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\+
| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\=
| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
```assembly
movu [srcq], m0
```
C’est ce qu’on appelle un stockage (*store*).Les données sont écrites en mémoire à l’adresse pointée par `srcq`.
```assembly
RET
```
`RET` est une macro indiquant que la fonction se termine.Presque toutes les fonctions en assembleur dans FFmpeg modifient les données passées en argument au lieu de retourner une valeur.
Comme vous le verrez dans l’exercice, nous créerons des pointeurs de fonction vers des fonctions en assembleur et les utiliserons lorsque cela sera possible.
[Leçon suivante](../lesson_02/index.fr.md)
================================================
FILE: lesson_01/index.md
================================================
**FFmpeg Assembly Language Lesson One**
**Introduction**
Welcome to the FFmpeg School of Assembly Language. You have taken the first step on the most interesting, challenging, and rewarding journey in programming. These lessons will give you a grounding in the way assembly language is written in FFmpeg and open your eyes to what's actually going on in your computer..
**Required Knowledge**
* Knowledge of C, in particular pointers. If you don't know C, work through [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) book
* High School Mathematics (scalar vs vector, addition, multiplication etc)
**What is assembly language?**
Assembly language is a programming language where you write code that directly corresponds to the instructions a CPU processes. Human readable assembly language is, as the name suggests, *assembled* into binary data, known as *machine code*, that the CPU can understand. You might see assembly language code referred to as “assembly” or “asm” for short.
The vast majority of assembly code in FFmpeg is what's known as *SIMD, Single Instruction Multiple Data*. SIMD is sometimes referred to as vector programming. This means that a particular instruction operates on multiple elements of data at the same time. Most programming languages operate on one data element at a time, known as scalar programming.
As you might have guessed, SIMD lends itself well to processing images, video, and audio which have lots of data ordered sequentially in memory. There are specialist instructions available in the CPU to help us process sequential data.
In FFmpeg, you'll see the terms “assembly function”, “SIMD”, and “vector(ise)” used interchangeably. They all refer to the same thing: Writing a function in assembly language by hand to process multiple elements of data in one go. Some projects may also refer to these as “assembly kernels”.
All of this might sound complicated, but it's important to remember that in FFmpeg, high schoolers have written assembly code. As with everything, learning is 50% jargon and 50% actual learning.
**Why do we write in assembly language?**
To make multimedia processing fast. It’s very common to get a 10x or more speed improvement from writing assembly code, which is especially important when wanting to play videos in real time without stuttering. It also saves energy and extends battery life. It’s worth pointing out that video encode and decode functions are some of the most heavily used functions on earth, both by end-users and by big companies in their datacentres. So even a small improvement adds up quickly.
You’ll often see, online, people use *intrinsics,* which are C-like functions that map to assembly instructions to allow for faster development. In FFmpeg we don’t use intrinsics but instead write assembly code by hand. This is an area of controversy, but intrinsics are typically around 10-15% slower than hand-written assembly (intrinsics supporters would disagree), depending on the compiler. For FFmpeg, every bit of extra performance helps, which is why we write in assembly code directly. There’s also an argument that intrinsics are difficult to read owing to their use of “[Hungarian Notation](https://en.wikipedia.org/wiki/Hungarian_notation)”.
You may also see *inline assembly* (i.e. not using intrinsics) remaining in a few places in FFmpeg for historical reasons, or in projects like the Linux Kernel because of very specific use cases there. This is where assembly code is not in a separate file, but written inline with C code. The prevailing opinion in projects like FFmpeg is that this code is hard to read, not widely supported by compilers and unmaintainable.
Lastly, you’ll see a lot of self-proclaimed experts online saying none of this is necessary and the compiler can do all of this “vectorisation” for you. At least for the purpose of learning, ignore them: recent tests in e.g. [the dav1d project](https://www.videolan.org/projects/dav1d.html) showed around a 2x speedup from this automatic vectorisation, while the hand-written versions could reach 8x.
**Flavours of assembly language**
These lessons will focus on x86 64-bit assembly language. This is also known as amd64, although it still works on Intel CPUs. There are other types of assembly for other CPUs like ARM and RISC-V and potentially in the future these lessons will be extended to cover those.
There are two flavours of x86 assembly syntax that you’ll see online: AT&T and Intel. AT&T Syntax is older and harder to read compared to Intel syntax. So we will use Intel syntax.
**Supporting materials**
You might be surprised to hear that books or online resources like Stack Overflow are not particularly helpful as references. This is in part because of our choice to use handwritten assembly with Intel syntax. But also because a lot of online resources are focused on operating system programming or hardware programming, usually using non-SIMD code. FFmpeg assembly is particularly focused on high performance image processing, and as you’ll see it’s a particularly unique approach to assembly programming. That said, it’s easy to understand other assembly use-cases once you’ve completed these lessons
Many books go into a lot of computer architecture details before teaching assembly. This is fine if that’s what you want to learn, but from our standpoint, it’s like studying engines before learning to drive a car.
That said, the diagrams in the later parts of “The Art of 64-bit assembly” book showing SIMD instructions and their behaviour in a visual form are helpful: [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/)
A discord server is available to answer questions:
[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB)
**Registers**
Registers are areas in the CPU where data can be processed. CPUs don’t operate on memory directly, but instead data is loaded into registers, processed, and written back to memory. In assembly language, generally, you cannot directly copy data from one memory location to another without first passing that data through a register.
**General Purpose Registers**
The first type of register is what is known as a General Purpose Register (GPR). GPRs are referred to as general purpose because they can contain either data, in this case up to a 64-bit value, or a memory address (a pointer). A value in a GPR can be processed through operations like addition, multiplication, shifting, etc.
In most assembly books, there are whole chapters dedicated to the subtleties of GPRs, the historical background etc. This is because GPRs are important when it comes to operating system programming, reverse engineering, etc. In the assembly code written in FFmpeg, GPRs are more like scaffolding and most of the time their complexities are not needed and abstracted away.
**Vector registers**
Vector (SIMD) registers, as the name suggests, contain multiple data elements. There are various types of vector registers:
* mm registers - MMX registers, 64-bit sized, historic and not used much any more
* xmm registers - XMM registers, 128-bit sized, widely available
* ymm registers - YMM registers, 256-bit sized, some complications when using these
* zmm registers - ZMM registers, 512-bit sized, limited availability
Most calculations in video compression and decompression are integer-based so we’ll stick to that. Here’s an example of 16 bytes in an xmm register:
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
But it could be eight words (16-bit integers)
| a | b | c | d | e | f | g | h |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Or four double words (32-bit integers)
| a | b | c | d |
| :---- | :---- | :---- | :---- |
Or two quadwords (64-bit integers):
| a | b |
| :---- | :---- |
To recap:
* **b**ytes - 8-bit data
* **w**ords - 16-bit data
* **d**oublewords - 32-bit data
* **q**uadwords - 64-bit data
* **d**ouble **q**uadwords - 128-bit data
The bold characters will be important later.
**x86inc.asm include**
You’ll see in many examples we include the file x86inc.asm. X86inc.asm is a lightweight abstraction layer used in FFmpeg, x264, and dav1d to make an assembly programmer's life easier. It helps in many ways, but to begin with, one of the useful things it does is it labels GPRs, r0, r1, r2. This means you don’t have to remember any register names. As mentioned before, GPRs are generally just scaffolding so this makes life a lot easier.
**A simple scalar asm snippet**
Let’s look at a simple (and very much artificial) snippet of scalar asm (assembly code that operates on individual data items, one at a time, within each instruction) to see what’s going on:
```assembly
mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
```
In the first line, the *immediate value* 3 (a value stored directly in the assembly code itself as opposed to a value fetched from memory) is being stored into register r0 as a quadword. Note that in Intel syntax, the source operand (the value or location providing the data, located on the right) is transferred to the destination operand (the location receiving the data, located on the left), much like the behavior of memcpy. You can also read it as “r0q = 3”, since the order is the same. The “q” suffix of r0 designates the register as being used as a quadword. inc increments the value so that r0q contains 4, dec decrements the value back to 3. imul multiplies the value by 5. So at the end, r0q contains 15.
Note that the human readable instructions such as mov and inc, which are assembled into machine code by the assembler, are known as *mnemonics*. You may see online and in books mnemonics represented with capital letters like MOV and INC but these are the same as the lower case versions. In FFmpeg, we use lower case mnemonics and keep upper case reserved for macros.
**Understanding a basic vector function**
Here’s our first SIMD function:
```assembly
%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
```
Let’s go through it line by line:
```assembly
%include "x86inc.asm"
```
This is a “header” developed in the x264, FFmpeg, and dav1d communities to provide helpers, predefined names and macros (such as cglobal below) to simplify writing assembly.
```assembly
SECTION .text
```
This denotes the section where the code you want to execute is placed. This is in contrast to the .data section, where you can put constant data.
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
```
The first line is a comment (the semi-colon “;” in asm is like “//” in C) showing what the function argument looks like in C. The second line shows how we are initialising the function to use XMM registers, using the sse2 instruction set. This is because paddb is an sse2 instruction. We’ll cover sse2 in more detail in the next lesson.
```assembly
cglobal add_values, 2, 2, 2, src, src2
```
This is an important line as it defines a C function called “add_values”.
Let’s go through each item one at a time:
* The next parameter shows it has two function arguments.
* The parameter after that shows that we’ll use two GPRs in this function, including the arguments. In some cases we might want to use more GPRs so we have to tell x86util we need more.
* The parameter after that tells x86util how many XMM registers we are going to use.
* The following two parameters are labels for the function arguments.
It’s worth noting that older code may not have labels for the function arguments but instead address GPRs directly using r0, r1 etc.
```assembly
movu m0, [srcq]
movu m1, [src2q]
```
movu is shorthand for movdqu (move double quad unaligned). Alignment will be covered in another lesson but for now movu can be treated as a 128-bit move from [srcq]. In the case of mov, the brackets mean that the address in [srcq] is being dereferenced, the equivalent of **src in C.* This is what’s known as a load. Note that the “q” suffix refers to the size of the pointer *(*i.e in C it represents *sizeof(*src) == 8 on 64-bit systems, and x86asm is smart enough to use 32-bit on 32-bit systems) but the underlying load is 128-bit.
Note that we don’t refer to vector registers by their full name, in this case xmm0,but as m0, an abstracted form. In future lessons you’ll see how this means you can write code once and have it work on multiple SIMD register sizes.
```assembly
paddb m0, m1
```
paddb (read this in your head as *p-add-b*) is adding each byte in each register as shown below. The “p” prefix stands for “packed” and is used to identify vector instructions vs scalar instructions. The “b” suffix shows that this is bytewise addition (addition of bytes).
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\+
| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\=
| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
```assembly
movu [srcq], m0
```
This is what’s known as a store. The data is written back to the address in the srcq pointer.
```assembly
RET
```
This is a macro to denote the function returns. Virtually all assembly functions in FFmpeg modify the data in the arguments as opposed to returning a value.
As you’ll see in the assignment, we create function pointers to assembly functions and use them where available.
[Next Lesson](../lesson_02/index.md)
================================================
FILE: lesson_01/index.tr.md
================================================
**FFmpeg Assembly Dili Birinci Ders**
**Giriş**
FFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, zorlu ve ödüllendirici yolculuğunda ilk adımı attınız. Bu dersler, assembly dilinin FFmpeg'de nasıl yazıldığı konusunda size temel bilgi verecek ve bilgisayarınızda gerçekte neler olup bittiğine gözlerinizi açacaktır.
**Gerekli Bilgiler**
* C bilgisi, özellikle pointer'lar. Eğer C bilmiyorsanız, [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) kitabını çalışın
* Lise Matematiği (skalar vs vektör, toplama, çarpma vb.)
**Assembly dili nedir?**
Assembly dili, CPU'nun işlediği talimatlara doğrudan karşılık gelen kod yazdığınız bir programlama dilidir. İnsan tarafından okunabilir assembly dili, adından da anlaşılacağı gibi, CPU'nun anlayabileceği *makine kodu* olarak bilinen ikili veriye *derlenir*. Assembly dili kodunun kısaca "assembly" veya "asm" olarak anıldığını görebilirsiniz.
FFmpeg'deki assembly kodunun büyük çoğunluğu *SIMD, Single Instruction Multiple Data* (Tek Talimat Çoklu Veri) olarak bilinen türdedir. SIMD bazen vektör programlama olarak da anılır. Bu, belirli bir talimatın aynı anda birden fazla veri öğesi üzerinde çalıştığı anlamına gelir. Çoğu programlama dili bir seferde bir veri öğesi üzerinde çalışır, bu skalar programlama olarak bilinir.
Tahmin edebileceğiniz gibi, SIMD kendisini bellekte sıralı olarak düzenlenmiş çok miktarda veriye sahip görüntü, video ve ses işlemeye iyi uyarlar. CPU'da sıralı veriyi işlememize yardımcı olacak özel talimatlar mevcuttur.
FFmpeg'de "assembly fonksiyonu", "SIMD" ve "vektör(leştirme)" terimlerinin birbirinin yerine kullanıldığını göreceksiniz. Hepsi aynı şeyi ifade eder: Birden fazla veri öğesini tek seferde işlemek için elle assembly dilinde bir fonksiyon yazmak. Bazı projeler bunları "assembly kernel'leri" olarak da adlandırabilir.
Tüm bunlar karmaşık gelebilir, ancak FFmpeg'de lise öğrencilerinin assembly kodu yazdığını hatırlamak önemlidir. Her şeyde olduğu gibi, öğrenme %50 jargon ve %50 gerçek öğrenmedir.
**Neden assembly dilinde yazıyoruz?**
Multimedya işlemeyi hızlı hale getirmek için. Assembly kodu yazmaktan 10 kat veya daha fazla hız iyileştirmesi elde etmek çok yaygındır, bu özellikle videoları takılma olmadan gerçek zamanlı oynatmak istediğinizde önemlidir. Ayrıca enerji tasarrufu sağlar ve pil ömrünü uzatır. Video kodlama ve kod çözme fonksiyonlarının hem son kullanıcılar hem de büyük şirketlerin veri merkezlerinde dünyadaki en yoğun kullanılan fonksiyonlardan bazıları olduğunu belirtmek gerekir. Bu nedenle küçük bir iyileştirme bile hızla büyük sonuçlar doğurur.
Çevrimiçi olarak, insanların daha hızlı geliştirme için assembly talimatlarına eşlenen C benzeri fonksiyonlar olan *intrinsic'ler* kullandığını sık sık görürsünüz. FFmpeg'de intrinsic kullanmayız, bunun yerine assembly kodunu elle yazarız. Bu tartışmalı bir alan olmakla birlikte, intrinsic'ler genellikle derleyiciye bağlı olarak elle yazılmış assembly'den yaklaşık %10-15 daha yavaştır (intrinsic destekçileri buna katılmaz). FFmpeg için her parça ekstra performans yardımcı olur, bu yüzden assembly kodunu doğrudan yazarız. Ayrıca intrinsic'lerin "[Hungarian Notation](https://en.wikipedia.org/wiki/Hungarian_notation)" kullanımları nedeniyle okunması zor olduğu argümanı da vardır.
Ayrıca FFmpeg'de tarihsel nedenlerle veya Linux Kernel gibi projelerde çok özel kullanım durumları nedeniyle birkaç yerde *inline assembly* (yani intrinsic kullanmayan) kalıntıları görebilirsiniz. Bu, assembly kodunun ayrı bir dosyada değil, C kodu ile satır içinde yazıldığı durumdur. FFmpeg gibi projelerdeki genel kanı, bu kodun okunması zor, derleyiciler tarafından yaygın olarak desteklenmeyen ve bakımı yapılamayan kod olduğudur.
Son olarak, çevrimiçi birçok sözde uzmanın bunların hiçbirinin gerekli olmadığını ve derleyicinin sizin için tüm bu "vektörleştirmeyi" yapabileceğini söylediğini göreceksiniz. En azından öğrenme amacıyla, onları görmezden gelin: örneğin [dav1d projesi](https://www.videolan.org/projects/dav1d.html)'ndeki son testler bu otomatik vektörleştirmeden yaklaşık 2 kat hızlanma gösterirken, elle yazılmış sürümler 8 kata ulaşabiliyordu.
**Assembly dili çeşitleri**
Bu dersler x86 64-bit assembly diline odaklanacak. Bu amd64 olarak da bilinir, ancak Intel CPU'larında da çalışır. ARM ve RISC-V gibi diğer CPU'lar için başka assembly türleri vardır ve potansiyel olarak gelecekte bu dersler bunları da kapsayacak şekilde genişletilebilir.
Çevrimiçi göreceğiniz x86 assembly sözdiziminin iki çeşidi vardır: AT&T ve Intel. AT&T Sözdizimi daha eskidir ve Intel sözdizimime kıyasla okunması daha zordur. Bu nedenle Intel sözdizimini kullanacağız.
**Destekleyici materyaller**
Kitapların veya Stack Overflow gibi çevrimiçi kaynakların referans olarak özellikle yardımcı olmadığını duymak sizi şaşırtabilir. Bu kısmen Intel sözdizimi ile elle yazılmış assembly kullanma seçimimizden kaynaklanır. Ayrıca birçok çevrimiçi kaynak, genellikle SIMD olmayan kod kullanarak işletim sistemi programlama veya donanım programlamaya odaklanır. FFmpeg assembly'si özellikle yüksek performanslı görüntü işlemeye odaklanır ve göreceğiniz gibi assembly programlamaya özellikle benzersiz bir yaklaşımdır. Bununla birlikte, bu dersleri tamamladıktan sonra diğer assembly kullanım durumlarını anlamak kolaydır.
Birçok kitap assembly öğretmeden önce çok sayıda bilgisayar mimarisi detayına girer. Öğrenmek istediğiniz bu ise bu iyidir, ancak bizim bakış açımızdan, araba kullanmayı öğrenmeden önce motorları incelemek gibidir.
Bununla birlikte, "The Art of 64-bit assembly" kitabının sonraki bölümlerinde SIMD talimatlarını ve davranışlarını görsel formda gösteren diyagramlar yardımcıdır: [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/)
Soruları yanıtlamak için bir discord sunucusu mevcuttur:
[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB)
**Register'lar**
Register'lar CPU'da verinin işlenebileceği alanlardır. CPU'lar doğrudan bellek üzerinde çalışmaz, bunun yerine veri register'lara yüklenir, işlenir ve belleğe geri yazılır. Assembly dilinde, genellikle, veriyi önce bir register'dan geçirmeden bir bellek konumundan diğerine doğrudan kopyalayamazsınız.
**Genel Amaçlı Register'lar**
İlk register türü Genel Amaçlı Register (GPR) olarak bilinir. GPR'lar genel amaçlı olarak anılır çünkü bu durumda 64-bit'e kadar bir değer veya bir bellek adresi (bir pointer) içerebilirler. Bir GPR'daki değer toplama, çarpma, kaydırma vb. işlemlerle işlenebilir.
Çoğu assembly kitabında, GPR'ların inceliklerine, tarihsel geçmişe vb. adanmış tüm bölümler vardır. Bunun nedeni GPR'ların işletim sistemi programlama, tersine mühendislik vb. söz konusu olduğunda önemli olmalarıdır. FFmpeg'de yazılan assembly kodunda, GPR'lar daha çok iskelet gibidir ve çoğu zaman karmaşıklıkları gerekli değildir ve soyutlanmıştır.
**Vektör register'ları**
Vektör (SIMD) register'ları, adından da anlaşılacağı gibi, birden fazla veri öğesi içerir. Çeşitli vektör register türleri vardır:
* mm register'ları - MMX register'ları, 64-bit boyutunda, tarihsel ve artık pek kullanılmıyor
* xmm register'ları - XMM register'ları, 128-bit boyutunda, yaygın olarak mevcut
* ymm register'ları - YMM register'ları, 256-bit boyutunda, bunları kullanırken bazı komplikasyonlar var
* zmm register'ları - ZMM register'ları, 512-bit boyutunda, sınırlı erişilebilirlik
Video sıkıştırma ve açmadaki hesaplamaların çoğu integer tabanlıdır, bu nedenle buna sadık kalacağız. İşte bir xmm register'ında 16 byte örneği:
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Ancak sekiz word (16-bit integer) olabilir
| a | b | c | d | e | f | g | h |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
Veya dört double word (32-bit integer)
| a | b | c | d |
| :---- | :---- | :---- | :---- |
Veya iki quadword (64-bit integer):
| a | b |
| :---- | :---- |
Özetlemek gerekirse:
* **b**yte'lar - 8-bit veri
* **w**ord'ler - 16-bit veri
* **d**oubleword'ler - 32-bit veri
* **q**uadword'ler - 64-bit veri
* **d**ouble **q**uadword'ler - 128-bit veri
Kalın harfler daha sonra önemli olacak.
**x86inc.asm include**
Birçok örnekte x86inc.asm dosyasını dahil ettiğimizi göreceksiniz. X86inc.asm, FFmpeg, x264 ve dav1d'de bir assembly programcısının hayatını kolaylaştırmak için kullanılan hafif bir soyutlama katmanıdır. Birçok şekilde yardımcı olur, ancak başlangıç için faydalı yaptığı şeylerden biri GPR'ları r0, r1, r2 olarak etiketlemesidir. Bu, herhangi bir register adını hatırlamanız gerekmediği anlamına gelir. Daha önce bahsedildiği gibi, GPR'lar genellikle sadece iskelet olduğu için bu hayatı çok kolaylaştırır.
**Basit bir skalar asm parçacığı**
Neler olup bittiğini görmek için basit (ve oldukça yapay) bir skalar asm parçacığına (her talimat içinde aynı anda bireysel veri öğeleri üzerinde çalışan assembly kodu) bakalım:
```assembly
mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
```
İlk satırda, *immediate değer* 3 (bellekten getirilen bir değerin aksine doğrudan assembly kodunun kendisinde saklanan bir değer) r0 register'ına quadword olarak saklanıyor. Intel sözdiziminde, kaynak operand (veriyi sağlayan değer veya konum, sağda yer alır) hedef operand'a (veriyi alan konum, solda yer alır) aktarılır, tıpkı memcpy davranışı gibi. Bunu "r0q = 3" olarak da okuyabilirsiniz, çünkü sıra aynıdır. r0'ın "q" eki register'ın quadword olarak kullanıldığını belirtir. inc değeri artırır böylece r0q 4 içerir, dec değeri 3'e geri azaltır. imul değeri 5 ile çarpar. Sonunda, r0q 15 içerir.
mov ve inc gibi makine koduna derleyici tarafından derlenen insan tarafından okunabilir talimatların *mnemonik* olarak bilindiğini unutmayın. Çevrimiçi ve kitaplarda MOV ve INC gibi büyük harflerle temsil edilen mnemonikleri görebilirsiniz ancak bunlar küçük harf sürümleri ile aynıdır. FFmpeg'de küçük harf mnemonikleri kullanırız ve büyük harfleri makrolar için saklı tutarız.
**Temel bir vektör fonksiyonunu anlama**
İşte ilk SIMD fonksiyonumuz:
```assembly
%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
```
Satır satır gözden geçirelim:
```assembly
%include "x86inc.asm"
```
Bu, assembly yazmayı basitleştirmek için yardımcılar, önceden tanımlanmış isimler ve makrolar (aşağıdaki cglobal gibi) sağlamak amacıyla x264, FFmpeg ve dav1d topluluklarında geliştirilen bir "header"dır.
```assembly
SECTION .text
```
Bu, çalıştırmak istediğiniz kodun yerleştirildiği bölümü belirtir. Bu, sabit veri koyabileceğiniz .data bölümünün aksine.
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
```
İlk satır fonksiyon argümanının C'de nasıl göründüğünü gösteren bir yorumdur (assembly'de noktalı virgül ";" C'deki "//" gibidir). İkinci satır, paddb bir sse2 talimatı olduğu için sse2 talimat setini kullanarak XMM register'larını nasıl başlattığımızı gösterir. sse2'yi bir sonraki derste daha detaylı ele alacağız.
```assembly
cglobal add_values, 2, 2, 2, src, src2
```
Bu, "add_values" adlı bir C fonksiyonu tanımladığı için önemli bir satırdır.
Her öğeyi tek tek gözden geçirelim:
* Sonraki parametre iki fonksiyon argümanına sahip olduğunu gösterir.
* Bundan sonraki parametre argümanlar için iki GPR kullanacağımızı gösterir. Bazı durumlarda daha fazla GPR kullanmak isteyebiliriz, bu nedenle x86util'e daha fazlasına ihtiyacımız olduğunu söylemek zorundayız.
* Bundan sonraki parametre x86util'e kaç XMM register'ı kullanacağımızı söyler.
* Takip eden iki parametre fonksiyon argümanları için etiketlerdir.
Eski kodların fonksiyon argümanları için etiket bulundurmayabileceğini, bunun yerine r0, r1 vb. kullanarak GPR'lara doğrudan hitap edebileceğini belirtmek gerekir.
```assembly
movu m0, [srcq]
movu m1, [src2q]
```
movu, movdqu'nun (move double quad unaligned - hizalanmamış çift quad taşıma) kısaltmasıdır. Hizalama başka bir derste ele alınacak ancak şimdilik movu, [srcq]'den 128-bit taşıma olarak değerlendirilebilir. mov durumunda, köşeli parantezler [srcq]'deki adresin referans alındığı anlamına gelir, C'de *src'nin eşdeğeridir. Bu *yükleme* olarak bilinir. "q" ekinin pointer'ın boyutunu ifade ettiğini (yani C'de sizeof(*src) == 64-bit sistemlerde 8 ve x86asm 32-bit sistemlerde 32-bit kullanacak kadar akıllı) ancak temel yüklemenin 128-bit olduğunu unutmayın.
Vektör register'larını bu durumda xmm0 olan tam adlarıyla değil, soyutlanmış bir form olan m0 olarak andığımızı unutmayın. Gelecek derslerde bunun kod yazmayı bir kez yapıp birden fazla SIMD register boyutunda çalışmasını nasıl sağladığını göreceksiniz.
```assembly
paddb m0, m1
```
paddb (bunu kafanızda *p-add-b* olarak okuyun) aşağıda gösterildiği gibi her register'daki her byte'ı topluyor. "p" ön eki "packed" (paketlenmiş) anlamına gelir ve vektör talimatları ile skalar talimatları tanımlamak için kullanılır. "b" eki bunun byte'lık toplama (byte toplamı) olduğunu gösterir.
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\+
| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\=
| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
```assembly
movu [srcq], m0
```
Bu *saklama* olarak bilinir. Veri srcq pointer'ındaki adrese geri yazılır.
```assembly
RET
```
Bu, fonksiyonun döndüğünü belirten bir makrodur. FFmpeg'deki assembly fonksiyonlarının neredeyse tamamı bir değer döndürmek yerine argümanlardaki veriyi değiştirir.
Ödevde göreceğiniz gibi, assembly fonksiyonlarına fonksiyon pointer'ları oluşturuyoruz ve mevcut olduklarında bunları kullanıyoruz.
[Sonraki Ders](../lesson_02/index.md)
================================================
FILE: lesson_01/index.zh.md
================================================
**FFmpeg 汇编语言第一课**
**简介**
欢迎来到 FFmpeg 汇编语言学校。你已经迈出了编程中最有趣、最具挑战、最具回报的一段旅程的第一步。这些课程将为你打下 FFmpeg 中汇编语言编写的基础,并让你大开眼界,了解计算机内部的实际运作方式。
**必备知识**
* C 语言知识,特别是指针。如果你不了解 C,请通读 [C程序设计语言](https://en.wikipedia.org/wiki/The_C_Programming_Language) 这本书
* 高中数学(标量与向量,加法,乘法等)
**什么是汇编语言?**
汇编语言是一种编程语言,你编写的代码直接对应于 CPU 处理的指令。顾名思义,人类可读的汇编语言被*汇编*(assembled)成二进制数据,称为*机器码*(machine code),即 CPU 可以理解的代码。你可能会看到汇编语言代码被简称为 “assembly” 或 “asm”。
FFmpeg 中的绝大多数汇编代码都是所谓的 *SIMD(Single Instruction Multiple Data,即单指令多数据)*。SIMD 有时被称为向量编程(vector programming)。这意味着一条特定指令同时对多个数据元素进行操作。大多数编程语言一次操作一个数据元素,称为标量编程(scalar programming)。
你可能已经猜到了,SIMD 非常适合处理图像、视频和音频,因为这些数据在内存中按顺序大量排列。CPU 中提供了专门的指令来帮助处理顺序数据。
在 FFmpeg 中,你会看到术语 “assembly function”(汇编函数)、“SIMD” 和 “vector(ise)”(向量化)互换使用。它们都指代同一件事:手工编写汇编语言函数,一次性处理多个数据元素。有些项目也可能将其称为 “assembly kernels”(汇编内核)。
所有这些听起来可能很复杂,但要记住,在 FFmpeg 中,甚至有高中生编写过汇编代码。和其他事情一样,学习过程是 50% 的行话和 50% 的实际学习。
**为什么要用汇编语言编写?**
为了让多媒体处理变得更快。编写汇编代码通常能获得 10 倍或更高的速度提升,这对于想要流畅实时播放视频尤为重要。它还能节省能源并延长电池寿命。值得指出的是,视频编码和解码函数是地球上使用最频繁的功能之一,无论是终端用户还是大公司的数据中心都在使用。因此,即使是微小的改进也会迅速累积成显著的效果。
你在网上经常会看到人们使用 *intrinsics*(内置函数),这是一种类似 C 的函数,映射到汇编指令以加快开发速度。在 FFmpeg 中,我们不使用 intrinsics,而是手工编写汇编代码。这是一个有争议的领域,但 intrinsics 通常比手写汇编慢 10-15% 左右(intrinsics 支持者可能会不同意),具体取决于编译器。对于 FFmpeg 来说,每一分额外的性能都有帮助,这就是为什么直接用汇编代码编写。还有一个论点是,intrinsics 由于使用了 “[匈牙利命名法](https://en.wikipedia.org/wiki/Hungarian_notation)” 而难以阅读。
你也可能在 FFmpeg 的少数地方看到由于历史原因保留的 *inline assembly(内联汇编)*(即不使用 intrinsics),或者在像 Linux 内核这样的项目中因为非常特定的用例而看到它。这是指汇编代码不在单独的文件中,而是内联写在 C 代码中。在像 FFmpeg 这样的项目中,普遍的观点是这种代码难以阅读,编译器支持不广泛,且难以维护。
最后,你会看到网上有很多自封的专家说这些都没必要,编译器可以为你做所有的 “向量化”。至少为了学习的目的,忽略他们:最近在例如 [dav1d 项目](https://www.videolan.org/projects/dav1d.html) 中的测试显示,这种自动向量化大约能带来 2 倍的加速,而手写版本可以达到 8 倍。
**汇编语言的种类**
本课程将专注于 x86 64 位汇编语言。这也称为 amd64,尽管它仍然适用于 Intel CPU。还有针对其他 CPU(如 ARM 和 RISC-V)的其他类型的汇编,未来本课程可能会扩展到涵盖那些内容。
你在网上会看到两种 x86 汇编语法风格:AT&T 和 Intel。与 Intel 语法相比,AT&T 语法较老且难以阅读。所以本课程将使用 Intel 语法。
**辅助材料**
你可能会惊讶地发现,书籍或像 Stack Overflow 这样的在线资源作为参考并不是特别有帮助。部分原因是本课程选择使用 Intel 语法的手写汇编。但也因为很多在线资源专注于操作系统编程或硬件编程,通常使用非 SIMD 代码。FFmpeg 汇编特别专注于高性能图像处理,正如你将看到的,这是一种独特的汇编编程方法。也就是说,一旦你完成了这些课程,就很容易理解其他汇编用例。
许多书在教汇编之前会深入讲解大量计算机架构细节。如果你想学的就是那个,那没问题,但从本课程的立场来看,这就像在学开车之前先研究引擎一样。
话虽如此,“The Art of 64-bit assembly” 一书后半部分展示 SIMD 指令及其行为的图表非常有帮助:[https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/)
有一个 discord 服务器可供提问:
[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB)
**寄存器**
寄存器是 CPU 中可以处理数据的区域。CPU 不直接操作内存,而是将数据加载到寄存器中,进行处理,然后写回内存。在汇编语言中,通常情况下,你不能直接将数据从一个内存位置复制到另一个位置,而需先通过一个寄存器传递该数据。
**通用寄存器**
第一种类型的寄存器被称为通用寄存器(General Purpose Register,GPR)。GPR 被称为通用是因为它们既可以包含数据(在这里最高为 64 位值),也可以包含内存地址(指针)。GPR 中的值可以通过加法、乘法、移位等操作进行处理。
在大多数汇编书中,有整章专门介绍 GPR 的细微差别、历史背景等。这是因为 GPR 在涉及操作系统编程、逆向工程等时非常重要。在 FFmpeg 编写的汇编代码中,GPR 更像是脚手架,大多数时候它们的复杂性是不需要的,并且被抽象掉了。
**向量寄存器**
向量(SIMD)寄存器,顾名思义,包含多个数据元素。有各种类型的向量寄存器:
* mm 寄存器 - MMX 寄存器,64 位大小,历史悠久但现在不常用
* xmm 寄存器 - XMM 寄存器,128 位大小,广泛可用
* ymm 寄存器 - YMM 寄存器,256 位大小,使用时有一些复杂性
* zmm 寄存器 - ZMM 寄存器,512 位大小,可用性有限
视频压缩和解压缩中的大多数计算都是基于整数的,所以本课程将坚持使用整数。这是一个 xmm 寄存器中 16 个字节的示例:
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
但它也可以是 8 个字(16 位整数)
| a | b | c | d | e | f | g | h |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
或者 4 个双字(32 位整数)
| a | b | c | d |
| :---- | :---- | :---- | :---- |
或者 2 个四字(64 位整数):
| a | b |
| :---- | :---- |
回顾一下:
* **b**ytes - 8 位数据(字节)
* **w**ords - 16 位数据(字)
* **d**oublewords - 32 位数据(双字)
* **q**uadwords - 64 位数据(四字)
* **d**ouble **q**uadwords - 128 位数据(双四字)
粗体字符稍后会很重要。
**x86inc.asm include**
你会看到在许多例子中包含了 x86inc.asm 文件。x86inc.asm 是 x264、FFmpeg 和 dav1d 中使用的一个轻量级抽象层,旨在让汇编程序员的工作更轻松。它在很多方面都有帮助,首先,它将 GPR 标记为 r0、r1、r2 等。这意味着你不必记住任何寄存器名称。如前所述,GPR 通常只是脚手架,所以这让事情简单了很多。
**一个简单的标量 asm 片段**
来看一个简单的(也是非常人为构造的)标量 asm 片段(即每条指令只处理一个数据项的汇编代码),看看发生了什么:
```assembly
mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
```
在第一行中,*立即数*(immediate value)3(直接存储在汇编代码本身中的值,而不是从内存中获取的值)被作为四字存储到寄存器 r0 中。注意在 Intel 语法中,源操作数(source operand,提供数据的值或位置,位于右侧)被传输到目标操作数(destination operand,接收数据的位置,位于左侧),就像 memcpy 的行为一样。既然顺序相同,你也可以将其读作 “r0q = 3”。r0 的 “q” 后缀将寄存器指定为用作四字。inc 增加该值,使 r0q 包含 4,dec 将该值减回 3。imul 将该值乘以 5。所以最后,r0q 包含 15。
注意,像 mov 和 inc 这样的人类可读指令,被汇编器汇编成机器码,被称为 *mnemonics(助记符)*。你可能会在网上和书中看到用大写字母表示的助记符,如 MOV 和 INC,但这些与小写版本相同。在 FFmpeg 中,我们使用小写助记符,并保留大写用于宏。
**理解一个基本的向量函数**
这是第一个 SIMD 函数:
```assembly
%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
```
逐行分析如下:
```assembly
%include "x86inc.asm"
```
这是 x264、FFmpeg 和 dav1d 社区开发的 “头文件”,提供辅助程序、预定义名称和宏(如下面的 cglobal)以简化编写汇编。
```assembly
SECTION .text
```
这表示你要执行的代码所在的段。这与 .data 段相反,你可以在那里放置常量数据。
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2)
INIT_XMM sse2
```
第一行是一个注释(asm 中的分号 “;” 就像 C 中的 “//”)展示了 C 中的函数参数是什么样的。第二行展示了如何初始化函数以使用 XMM 寄存器,使用 sse2 指令集。这是因为 paddb 是一个 sse2 指令。下一课将更详细地介绍 sse2。
```assembly
cglobal add_values, 2, 2, 2, src, src2
```
这是一行重要的代码,因为它定义了一个名为 “add_values” 的 C 函数。
逐项分析如下:
* 下一个参数显示它有两个函数参数。
* 再下一个参数显示将在这个函数中使用两个 GPR,包括参数。在某些情况下,可能想要使用更多的 GPR,所以必须告诉 x86util 需要更多。
* 再下一个参数告诉 x86util 将使用多少个 XMM 寄存器。
* 接下来的两个参数是函数参数的标签。
值得注意的是,较旧的代码可能没有函数参数的标签,而是直接使用 r0, r1 等寻址 GPR。
```assembly
movu m0, [srcq]
movu m1, [src2q]
```
movu 是 movdqu(move double quad unaligned,即移动双四字未对齐)的简写。对齐将在另一课中介绍,但现在 movu 可以被视为从 [srcq] 进行的 128 位移动。在 mov 的情况下,括号意味着 [srcq] 中的地址被解引用,相当于 C 中的 \*src。这就是所谓的加载(load)。注意 "q" 后缀指的是指针的大小(即在 C 中 sizeof(*src) == 8,在 64 位系统上,x86asm 足够聪明在 32 位系统上使用 32 位),但底层的加载是 128 位的。
注意这里不通过全名引用向量寄存器,在这种情况下是 xmm0,而是作为 m0,一种抽象形式。在未来的课程中,你将看到这意味着你可以编写一次代码,让它在多种 SIMD 寄存器大小上工作。
```assembly
paddb m0, m1
```
paddb(在脑海中读作 *p-add-b*)正在将每个寄存器中的每个字节相加,如下所示。“p” 前缀代表 “packed”(打包),用于识别向量指令与标量指令。“b” 后缀表示这是逐字节加法(字节的加法)。
| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\+
| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
\=
| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
```assembly
movu [srcq], m0
```
这就是所谓的存储(store)。数据被写回 srcq 指针中的地址。
```assembly
RET
```
这是一个表示函数返回的宏。实际上 FFmpeg 中的所有汇编函数都会修改参数中的数据,而不是返回值。
正如你在作业中会看到的,可以创建指向汇编函数的函数指针,并在可用的地方使用它们。
[下一课](../lesson_02/index.zh.md)
================================================
FILE: lesson_02/index.es.md
================================================
**Lección Dos de Lenguaje Ensamblador con FFmpeg**
Ahora que has escrito tu primera función en lenguaje ensamblador, vamos a introducir las *ramificaciones* (branches) y los *bucles* (loops).
Primero necesitamos presentar la idea de *etiquetas* (labels) y *saltos* (jumps). En el ejemplo artificial siguiente, la instrucción jmp hace que la ejecución salte a la instrucción que sigue a “.loop:”. “.loop:” se conoce como una *etiqueta*, y el punto delante del nombre indica que es una *etiqueta local*, lo que te permite reutilizar el mismo nombre de etiqueta en varias funciones. Este ejemplo, por supuesto, muestra un bucle infinito, pero más adelante lo ampliaremos con algo más realista.
```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```
Antes de hacer un bucle realista, tenemos que introducir el registro *FLAGS*. No nos detendremos demasiado en los entresijos de *FLAGS* (de nuevo, porque las operaciones con GPR son mayormente estructurales), pero hay varias *flags* como la Zero-Flag, Sign-Flag y Overflow-Flag que se configuran en función del resultado de la mayoría de las instrucciones (excepto mov) sobre datos escalares, como las operaciones aritméticas y los desplazamientos.
Aquí tienes un ejemplo donde el contador del bucle cuenta hacia atrás hasta llegar a cero, y jg (salta si es mayor que cero) es la condición de bucle. dec r0q modifica los FLAGS en función del valor de r0q después de la instrucción, y puedes saltar en base a esos FLAGS.
```assembly
mov r0q, 3
.loop:
; hacer algo
dec r0q
jg .loop ; saltar si es mayor que cero
```
Esto es equivalente al siguiente código en C:
```c
int i = 3;
do
{
// hacer algo
i--;
} while(i > 0)
```
Este código en C es un poco artificial. Normalmente un bucle en C se escribe así:
```c
int i;
for(i = 0; i < 3; i++) {
// hacer algo
}
```
Esto es aproximadamente equivalente a (no hay una forma directa de emparejar este bucle ```for```):
```assembly
xor r0q, r0q
.loop:
; hacer algo
inc r0q
cmp r0q, 3
jl .loop ; saltar si (r0q - 3) < 0, es decir, si (r0q < 3)
```
Hay varias cosas que destacar en este fragmento. La primera es ```xor r0q, r0q```, que es una forma común de poner un registro a cero, y en algunos sistemas es más rápida que ```mov r0q, 0```, porque, simplificando, no se realiza ninguna carga real. También se puede usar en registros SIMD con ```pxor m0, m0``` para poner a cero todo un registro. Lo siguiente a observar es el uso de cmp. cmp resta efectivamente el segundo registro del primero (sin almacenar el resultado en ningún sitio) y configura los *FLAGS*, pero como se indica en el comentario, se puede leer junto con el salto (jl = saltar si menor que cero) para saltar si ```r0q < 3```.
Fíjate cómo hay una instrucción extra (cmp) en este fragmento. En general, menos instrucciones significa código más rápido, por lo que se prefiere el fragmento anterior. Como verás en futuras lecciones, hay trucos adicionales para evitar esta instrucción extra y hacer que los *FLAGS* se configuren con una operación aritmética u otra instrucción. Observa que no estamos escribiendo ensamblador para que coincida exactamente con los bucles en C, sino para que sean lo más rápidos posible en ensamblador.
Aquí tienes algunos mnemónicos de salto comunes que acabarás usando (*FLAGS* incluidos por completitud, aunque no necesitas saber los detalles para escribir bucles):
| Mnemónico | Descripción | FLAGS |
| :-------- | :------------------------------------------- | :--------------- |
| JE/JZ | Saltar si Igual/Cero | ZF = 1 |
| JNE/JNZ | Saltar si No Igual/No Cero | ZF = 0 |
| JG/JNLE | Saltar si Mayor/No Menor o Igual (con signo) | ZF = 0 y SF = OF |
| JGE/JNL | Saltar si Mayor o Igual/No Menor (con signo) | SF = OF |
| JL/JNGE | Saltar si Menor/No Mayor o Igual (con signo) | SF ≠ OF |
| JLE/JNG | Saltar si Menor o Igual/No Mayor (con signo) | ZF = 1 o SF ≠ OF |
**Constantes**
Veamos algunos ejemplos que muestran cómo usar constantes:
```assembly
SECTION_RODATA
constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```
* SECTION_RODATA especifica que esta es una sección de datos de solo lectura. (Es un macro porque diferentes formatos de archivo de salida usados por los sistemas operativos lo declaran de forma diferente)
* constants_1: La etiqueta constants_1 se define como ```db``` (declarar byte), es decir, equivalente a uint8_t constants_1[4] = {1, 2, 3, 4};
* constants_2: Esto usa el macro ```times 2``` para repetir las palabras declaradas, es decir, equivalente a uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1};
Estas etiquetas, que el ensamblador convierte en direcciones de memoria, pueden usarse en cargas (pero no en escrituras, ya que son de solo lectura). Algunas instrucciones aceptan una dirección de memoria como operando, por lo que pueden usarse sin necesidad de cargar explícitamente en un registro (esto tiene ventajas e inconvenientes).
**Offsets (Desplazamientos)**
Los desplazamientos son la distancia (en bytes) entre elementos consecutivos en memoria. El desplazamiento lo determina el **tamaño de cada elemento** en la estructura de datos.
Ahora que sabemos escribir bucles, es hora de obtener datos. Pero hay algunas diferencias en comparación con C. Observemos el siguiente bucle en C:
```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```
El desplazamiento de 4 bytes entre elementos de data lo calcula de antemano el compilador de C. Pero cuando escribes ensamblador a mano, debes calcular estos desplazamientos tú mismo.
Veamos la sintaxis para el cálculo de direcciones de memoria. Esto se aplica a todo tipo de direcciones:
```assembly
[base + scale*index + disp]
```
* base – Es un GPR (normalmente un puntero pasado como argumento desde C)
* scale – Puede ser 1, 2, 4, 8. El valor por defecto es 1
* index – Es un GPR (normalmente un contador de bucle)
* disp – Es un entero (hasta 32 bits). El desplazamiento es un offset dentro de los datos
x86asm proporciona la constante mmsize, que indica el tamaño del registro SIMD con el que estás trabajando.
Aquí tienes un ejemplo simple (y sin sentido práctico) para ilustrar cómo cargar usando desplazamientos personalizados:
```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]
; hacer algunas cosas
add srcq, mmsize
dec r1q
jg .loop
RET
```
Fíjate cómo en ```movu m1, [srcq+2*r1q+3+mmsize]```, el ensamblador precalculará la constante de desplazamiento adecuada a usar. En la próxima lección te mostraremos un truco para evitar tener que hacer add y dec dentro del bucle, reemplazándolos por una sola instrucción add.
**LEA**
Ahora que entiendes los desplazamientos, puedes usar lea (*Load Effective Address*). Esto te permite realizar multiplicaciones y sumas en una sola instrucción, lo cual será más rápido que usar múltiples instrucciones. Por supuesto, hay limitaciones sobre con qué puedes multiplicar y qué puedes sumar, pero eso no impide que lea sea una instrucción muy potente.
```assembly
lea r0q, [base + scale*index + disp]
```
Contrario a lo que sugiere su nombre, LEA se puede usar para aritmética normal además del cálculo de direcciones. Puedes hacer algo tan complicado como:
```assembly
lea r0q, [r1q + 8*r2q + 5]
```
Ten en cuenta que esto no afecta al contenido de r1q ni de r2q. Tampoco afecta a los *FLAGS* (así que no puedes saltar en función del resultado). Usar LEA evita todas estas instrucciones y registros temporales (este código no es equivalente porque add sí modifica los *FLAGS*):
```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; desplazamiento aritmético a la izquierda en 3 = * 8
add r3q, 5
add r0q, r3q
```
Verás lea usado a menudo para preparar direcciones antes de bucles o realizar cálculos como el anterior. Ten en cuenta, por supuesto, que no puedes hacer cualquier tipo de multiplicación y suma, pero multiplicaciones por 1, 2, 4, 8 y la suma de un desplazamiento fijo son muy comunes.
En el ejercicio tendrás que cargar una constante y sumar los valores a un vector SIMD dentro de un bucle.
[Lección siguiente](../lesson_03/index.es.md)
================================================
FILE: lesson_02/index.fr.md
================================================
**FFmpeg Assembly Language Leçon Deux**
Maintenant que vous avez écrit votre première fonction en assembleur, nous allons vous présenter les branchements (*branch*) et les boucles (*loop*).
Dans un premier temps, nous devons vous introduire les notions de **labels** et de **jumps** (sauts). Regardons l'exemple suivant :
```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```
L'instruction `jmp` déplace l'exécution du code après `.loop:`. `.loop:` est ce qu'on appelle un **label**. Le **point** (.) préfixant le label indique que ce label est un **label local**. Cela signifie que vous pouvez réutiliser le même nom de label dans plusieurs fonctions sans provoquer de conflits.
Cette portion de code est un exemple de boucle infinie, nous l'améliorerons dans la suite des leçons pour avoir un exemple beaucoup plus réaliste.
Avant de faire une boucle réaliste, nous devons introduire le registre **FLAGS**. Nous n'allons pas rentrer dans les détails complexes des **FLAGS** (car les opérations sur les **GPR** sont principalement des échafaudages) mais sachez qu'il existe plusieurs *flags* comme le **Zero Flag** (**Z** ou **ZF**, pour *indicateur de zéro*), le **Sign Flag** (**SF**, pour *indicateur de signe*), et l'**Overflow Flag** (**OF**, pour *indicateur de débordement*) qui sont un ensemble d'indicateurs basés sur la sortie de la majorité des instructions (à l'exception des instructions de type `mov`) sur des données scalaires comme des opérations arithmétiques et des décalages (*shifts*).
Voici un exemple où le compteur `r0q` de la boucle décrémente jusqu'à zéro et où `jg` (*jump if greater than zero*) est utilisé comme condition d'arrêt. L'instruction `dec r0q` met à jour les **FLAGS** en fonction des valeurs de `r0q` après chaque décrémentation et vous *rebouclez* jusqu'à ce que `r0q` atteigne zéro, moment où la boucle s'arrête.
```assembly
mov r0q, 3
.loop:
; do something
dec r0q
jg .loop ; jump if greater than zero
```
L'équivalent en **C** est le suivant :
```c
int i = 3;
do
{
// do something
i--;
} while(i > 0)
```
Ce code en **C** est un peu contre-intuitif. Normalement, une boucle en **C** s'écrit de cette manière :
```c
int i;
for(i = 0; i < 3; i++) {
// do something
}
```
Les deux exemples sont quasiment équivalents à la portion en assembleur suivante (il n'y a pas de manière simple de trouver l'équivalent à cette boucle `for`):
```assembly
xor r0q, r0q
.loop:
inc r0q
; do something
cmp r0q, 3
jl .loop ; jump if (r0q - 3) < 0, i.e (r0q < 3)
```
Plusieurs choses sont à examiner sur cet extrait de code. Dans un premier temps, `xor r0q, r0q` est une manière simple d'assigner un registre à zéro, ce qui est plus rapide sur certains systèmes que `mov r0q, 0`, parce qu'il n'y a aucun chargement de données.
Cela peut aussi être utilisé avec des registres **SIMD** avec la ligne `pxor m0, m0` pour mettre à zéro un registre entier. La chose suivante à noter est l'utilisation de `cmp`. `cmp` peut effectivement soustraire le second registre du premier (sans avoir à sauvegarder la valeur quelque part) et met à jour les *FLAGS*, mais comme l'indique le commentaire, cela peut être lu avec l'instruction de saut `jl` (saut si inférieur à zéro) pour effectuer un saut si `r0q < 3`.
Notez la présence d'une instruction supplémentaire (````cmp```) dans ce court extrait. Généralement, peu d'instructions équivaut à du code plus rapide, c'est pourquoi l'exemple précédent est préféré. Comme vous le verrez plus tard, il existe plusieurs astuces pour éviter des instructions supplémentaires et faire en sorte que les *FLAGS* soient définis par une opération arithmétique ou une autre opération. Notez que nous n'écrivons pas de l'assembleur pour correspondre exactement aux boucles en C, nous écrivons des boucles en assembleur pour les rendre les plus rapides en assembleur.
Voici quelques mnémoniques de saut que vous finirez par utiliser (les registres *FLAGS* sont présentés là pour tout couvrir, mais vous n'aurez pas à tout connaître pour écrire des boucles) :
| Mnémonique | Signification | Description | FLAGS |
| :---- | :---- | :---- | :---- |
| JE/JZ | Jump if Equal/Zero | Saut si Égal / Zéro | ZF = 1 |
| JNE/JNZ | Jump if Not Equal/Not Zero | Saut si Non Égalité / Non Zéro | ZF = 0 |
| JG/JNLE | Jump if Greater/Not Less or Equal (signed) | Saut si Supériorité / Non Infériorité ou Égalité (signé) | ZF = 0 and SF = OF |
| JGE/JNL | Jump if Greater or Equal/Not Less (signed) | Saut si Supériorité ou Égalité / Non Infériorité (signé) | SF = OF |
| JL/JNGE | Jump if Less/Not Greater or Equal (signed) | Saut si Infériorité / Non Supériorité ou Égalité (signé) | SF ≠ OF |
| JLE/JNG | Jump if Less or Equal/Not Greater (signed) | Saut si Infériorité ou Égalité / Non Supériorité (signé) | ZF = 1 or SF ≠ OF |
**Constantes**
Plongeons dans quelques exemples sur l'utilisation des constantes:
```assembly
SECTION_RODATA
constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```
* SECTION\_RODATA indique qu'il s'agit d'une section en lecture seule. (C'est une macro car les différents formats de fichier de sortie utilisés par les systèmes d'exploitation les déclarent différemment.)
* le label *constants\_1* est défini comme étant un `db` (*declared bytes*), c'est équivalent à `uint8_t constants_1[4] = {1, 2, 3, 4};`
* constants\_2 utilise la macro `times 2` pour répéter la définition de mot (`dw` pour *declared word*), c'est équivalent à `uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1};`
Ces labels, que l'assembleur convertit en adresse mémoire, peuvent être utilisés pour les chargements (mais pas pour des stockages car ils sont en lecture seule). Quelques instructions acceptent une adresse mémoire comme opérande, ce qui permet de les utiliser sans chargement explicite dans un registre (il y a des avantages et des inconvénients à ceci).
**Offsets (déplacements)**
Les offsets (*déplacements*) sont les distances (en bytes) entre deux éléments consécutifs en mémoire. L'offset est déterminé par la **taille de chaque élément** dans la structure de données.
Maintenant que nous sommes capables d'écrire des boucles, il est temps de récupérer des données. Mais il existe des différences par rapport au C. Regardons la boucle suivante en C:
```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```
L'offset de 4 bytes entre chaque élément de données est pré-calculé par le compilateur. Mais en l'écrivant à la main en assembleur, vous devrez calculer cet offset vous-même.
Regardons la syntaxe du calcul des adresses mémoires. Cela s'applique à tout type d'adresses mémoires:
```assembly
[base + scale*index + disp]
```
* base - Il s'agit d'un GPR (généralement un pointeur provenant d'un argument d'une fonction en C).
* scale - Entier valant 1, 2, 4, 8. La valeur par défaut est 1.
* index - un registre GPR (le compteur d'une boucle).
* disp - Entier (stocké jusqu'à au plus 32 bits). disp est un décalage dans les données.
x86asm fournit la constante **mmsize**, qui vous indique la taille du registre SIMD avec lequel vous travaillez.
Voici un simple (et pas très logique) exemple d'illustration du chargement avec des déplacements différents:
```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]
; do some things
add srcq, mmsize
dec r1q
jg .loop
RET
```
Notez comment dans `movu m1, [srcq+2*r1q+3+mmsize]` l'assembleur pré-calculera le déplacement correct à utiliser. Dans la prochaine leçon, nous vous présenterons une astuce pour éviter d'avoir `add` et `dec` dans la boucle, en les remplaçant par un unique `add`.
**LEA**
Maintenant que vous maîtrisez les offsets, vous êtes capable d'utiliser l'instruction `lea` (*_**L**oad **E**ffective **A**ddress_*). Avec une seule instruction, vous êtes capable de faire une multiplication et une addition, ce qui est alors plus rapide que l'utilisation de plusieurs instructions. Il y a, bien sûr, des limitations sur les données que vous pouvez multiplier et ajouter mais cela n'empêche pas `lea` d'être une instruction puissante.
```assembly
lea r0q, [base + scale*index + disp]
```
Contrairement à son nom, **LEA** peut être utilisée autant pour des opérations arithmétiques que pour des calculs d'adresses mémoires. Vous pouvez faire quelque chose d'aussi compliqué que:
```assembly
lea r0q, [r1q + 8*r2q + 5]
```
Il est important de noter que cela n'affecte pas le contenu de `r1q` et `r2q`. Cela n'impacte pas non plus les registres *FLAGS* (par conséquent, vous ne pouvez pas effectuer de saut sur la sortie). L'utilisation des **LEA** évite toutes ces instructions et les registres temporaires (ce code n'a pas d'équivalent car `add` modifie les *FLAGS*):
```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; shift arithmetic left 3 = * 8
add r3q, 5
add r0q, r3q
```
Vous verrez l'utilisation de `lea` dans le calcul de beaucoup d'adresses avant des boucles ou pour faire des calculs comme le précédent. Notez bien que vous ne pouvez pas faire tous types de multiplications et d'additions, mais les multiplications par 1, 2, 4 ou 8 et les additions d'un décalage fixe sont des opérations courantes.
Dans l'exercice, vous aurez à charger une constante et à en additionner les valeurs à un vecteur **SIMD** dans une boucle.
[Leçon suivante](../lesson_03/index.fr.md)
================================================
FILE: lesson_02/index.md
================================================
**FFmpeg Assembly Language Lesson Two**
Now that you’ve written your first assembly language function, we will now introduce branches and loops.
We need to first introduce the idea of labels and jumps. In the artificial example below, the jmp instruction moves the code instruction to after “.loop:”. “.loop:” is known as a *label*, with the dot prefixing the label meaning it’s a *local label*, effectively allowing you to reuse the same label name across multiple functions. This example, of course, shows an infinite loop, but we’ll extend this later to something more realistic.
```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```
Before making a realistic loop we have to introduce the *FLAGS* register. We won’t dwell on the intricacies of *FLAGS* too much (again because GPR operations are largely scaffolding) but there are several flags such as Zero-Flag, Sign-Flag and Overflow-Flag which are set based on the output of most non-mov instructions on scalar data such as arithmetic operations and shifts.
Here’s an example where the loop counter counts down until zero and jg (jump if greater than zero) is the loop condition. dec r0q sets the FLAGs based on the value of r0q after the instruction and you can jump based on them.
```assembly
mov r0q, 3
.loop:
; do something
dec r0q
jg .loop ; jump if greater than zero
```
This is equivalent to the following C code:
```c
int i = 3;
do
{
// do something
i--;
} while(i > 0)
```
This C code is a bit unnatural. Usually a loop in C is written like this:
```c
int i;
for(i = 0; i < 3; i++) {
// do something
}
```
This is roughly equivalent to (there's no simple way of matching this ```for``` loop):
```assembly
xor r0q, r0q
.loop:
; do something
inc r0q
cmp r0q, 3
jl .loop ; jump if (r0q - 3) < 0, i.e (r0q < 3)
```
There are several things to point out in this snippet. First is ```xor r0q, r0q``` which is a common way of setting a register to zero, that on some systems is faster than ```mov r0q, 0```, because, put simply, there is no actual load taking place. It can also be used on SIMD registers with ```pxor m0, m0``` to zero out an entire register. The next thing to note is the use of cmp. cmp effectively subtracts the second register from the first (without storing the value anywhere) and sets *FLAGS*, but as per the comment, it can be read together with the jump, (jl = jump if less than zero) to jump if ```r0q < 3```.
Note how there is one extra instruction (cmp) in this snippet. Generally speaking, fewer instructions means faster code, which is why the earlier snippet is preferred. As you’ll see in future lessons, there are more tricks used to avoid this extra instruction and have *FLAGS* be set by arithmetic or another operation. Note how we are not writing assembly to match C loops exactly, we write loops to make them as fast as possible in assembly.
Here are some common jump mnemonics you’ll end up using (*FLAGS* are there for completeness, but you don’t need to know the specifics to write loops):
| Mnemonic | Description | FLAGS |
| :---- | :---- | :---- |
| JE/JZ | Jump if Equal/Zero | ZF = 1 |
| JNE/JNZ | Jump if Not Equal/Not Zero | ZF = 0 |
| JG/JNLE | Jump if Greater/Not Less or Equal (signed) | ZF = 0 and SF = OF |
| JGE/JNL | Jump if Greater or Equal/Not Less (signed) | SF = OF |
| JL/JNGE | Jump if Less/Not Greater or Equal (signed) | SF ≠ OF |
| JLE/JNG | Jump if Less or Equal/Not Greater (signed) | ZF = 1 or SF ≠ OF |
**Constants**
Let’s look at some examples showing how to use constants:
```assembly
SECTION_RODATA
constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```
* SECTION_RODATA specifies this is a read-only data section. (This is a macro because different output file formats that operating systems use declare this differently)
* constants_1: The label constants_1, is defined as ```db``` (declare byte) - i.e equivalent to uint8_t constants_1[4] = {1, 2, 3, 4};
* constants_2: This uses the ```times 2``` macro to repeat the declared words - i.e equivalent to uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1};
These labels, which the assembler converts to a memory address, can then be used in loads (but not stores as they are read-only). Some instructions take a memory address as an operand so they can be used without explicit loads into a register (there are pros and cons to this).
**Offsets**
Offsets are the distance (in bytes) between consecutive elements in memory. The offset is determined by the **size of each element** in the data structure.
Now that we're able to write loops, it’s time to fetch data. But there are some differences compared to C. Let’s look at the following loop in C:
```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```
The 4-byte offset between elements of data is precalculated by the C compiler. But when handwriting assembly you need to calculate these offsets yourself.
Let’s look at the syntax for memory address calculations. This applies to all types of memory addresses:
```assembly
[base + scale*index + disp]
```
* base - This is a GPR (usually a pointer from a C function argument)
* scale - This can be 1, 2, 4, 8. 1 is the default
* index - This is a GPR (usually a loop counter)
* disp - This is an integer (up to 32-bit). Displacement is an offset into the data
x86asm provides the constant mmsize, which lets you know the size of the SIMD register you are working with.
Here’s a simple (and nonsensical) example to illustrate loading from custom offsets:
```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]
; do some things
add srcq, mmsize
dec r1q
jg .loop
RET
```
Note how in ```movu m1, [srcq+2*r1q+3+mmsize]``` the assembler will precalculate the right displacement constant to use. In the next lesson we’ll show you a trick to avoid having to do add and dec in the loop, replacing them with a single add.
**LEA**
Now that you understand offsets you are able to use lea (Load Effective Address). This lets you perform multiplication and addition with one instruction, which is going to be faster than using multiple instructions. There are, of course, limitations on what you can multiply by and add to but this does not stop lea being a powerful instruction.
```assembly
lea r0q, [base + scale*index + disp]
```
Contrary to the name, LEA can be used for normal arithmetic as well as address calculations. You can do something as complicated as:
```assembly
lea r0q, [r1q + 8*r2q + 5]
```
Note that this does not affect the contents of r1q and r2q. It also doesn’t affect *FLAGS* (so you can’t jump based on the output). Using LEA avoids all these instructions and temporary registers (this code is not equivalent because add changes *FLAGS*):
```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; shift arithmetic left 3 = * 8
add r3q, 5
add r0q, r3q
```
You’ll see lea used a lot to set up addresses before loops or perform calculations like the above. Note of course, that you can’t do all types of multiply and addition, but multiplications by 1, 2, 4, 8 and addition of a fixed offset is common.
In the assignment you’ll have to load a constant and add the values to a SIMD vector in a loop.
[Next Lesson](../lesson_03/index.md)
================================================
FILE: lesson_02/index.tr.md
================================================
**FFmpeg Assembly Dili İkinci Ders**
Artık ilk assembly dili fonksiyonunuzu yazdığınıza göre, şimdi dallanma ve döngüleri tanıtacağız.
Önce etiket ve atlama fikrini tanıtmamız gerekiyor. Aşağıdaki yapay örnekte, jmp talimatı kod talimatını ".loop:" dan sonrasına taşır. ".loop:" *etiket* olarak bilinir, etiketin önündeki nokta bunun *yerel etiket* olduğu anlamına gelir, bu da aynı etiket adını birden fazla fonksiyon boyunca yeniden kullanmanıza olanak tanır. Bu örnek tabii ki sonsuz bir döngü gösteriyor, ancak bunu daha sonra daha gerçekçi bir şeye genişleteceğiz.
```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```
Gerçekçi bir döngü yapmadan önce *FLAGS* register'ını tanıtmamız gerekiyor. *FLAGS*'ın inceliklerine çok fazla dalacağımız yok (yine GPR işlemleri büyük ölçüde iskelet olduğu için) ancak Sıfır-Bayrağı, İşaret-Bayrağı ve Taşma-Bayrağı gibi birkaç bayrak vardır ve bunlar skalar veri üzerinde aritmetik işlemler ve kaydırmalar gibi mov olmayan talimatların çıktısına göre ayarlanır.
İşte döngü sayacının sıfıra kadar geriye saydığı ve jg'nin (sıfırdan büyükse atla) döngü koşulu olduğu bir örnek. dec r0q talimat sonrasında r0q değerine göre FLAGS'ı ayarlar ve bunlara göre atlayabilirsiniz.
```assembly
mov r0q, 3
.loop:
; bir şey yap
dec r0q
jg .loop ; sıfırdan büyükse atla
```
Bu aşağıdaki C koduna eşdeğerdir:
```c
int i = 3;
do
{
// bir şey yap
i--;
} while(i > 0)
```
Bu C kodu biraz doğal değil. Genellikle C'de bir döngü şöyle yazılır:
```c
int i;
for(i = 0; i < 3; i++) {
// bir şey yap
}
```
Bu kabaca şuna eşdeğerdir (bu ```for``` döngüsünü eşleştirmenin basit bir yolu yoktur):
```assembly
xor r0q, r0q
.loop:
; bir şey yap
inc r0q
cmp r0q, 3
jl .loop ; (r0q - 3) < 0 ise atla, yani (r0q < 3)
```
Bu parçacıkta işaret edilecek birkaç şey vardır. İlki ```xor r0q, r0q``` olup, bu bir register'ı sıfıra ayarlamanın yaygın bir yoludur ve bazı sistemlerde ```mov r0q, 0```'dan daha hızlıdır, çünkü basitçe söylemek gerekirse gerçek bir yükleme gerçekleşmez. ```pxor m0, m0``` ile SIMD register'larında tüm register'ı sıfırlamak için de kullanılabilir. Dikkat edilecek bir diğer şey ise cmp kullanımıdır. cmp etkili olarak ikinci register'ı birinciden çıkarır (değeri herhangi bir yerde saklamadan) ve *FLAGS*'ı ayarlar, ancak yorumda da belirtildiği gibi, ```r0q < 3``` ise atlamak için atlama ile birlikte okunabilir (jl = sıfırdan küçükse atla).
Bu parçacıkta bir ekstra talimat (cmp) olduğuna dikkat edin. Genel olarak, daha az talimat daha hızlı kod anlamına gelir, bu yüzden önceki parçacık tercih edilir. Gelecek derslerde göreceğiniz gibi, bu ekstra talimatı önlemek ve *FLAGS*'ın aritmetik veya başka bir işlemle ayarlanmasını sağlamak için daha fazla numara kullanılır. Assembly'de C döngülerini tam olarak eşleştirmek için assembly yazmadığımıza, assembly'de mümkün olduğunca hızlı hale getirmek için döngüler yazdığımıza dikkat edin.
İşte kullanacağınız bazı yaygın atlama mnemonikleri (*FLAGS* eksiksizlik için oradadır, ancak döngü yazmak için ayrıntıları bilmenize gerek yoktur):
| Mnemonic | Açıklama | FLAGS |
| :---- | :---- | :---- |
| JE/JZ | Eşitse/Sıfırsa Atla | ZF = 1 |
| JNE/JNZ | Eşit Değilse/Sıfır Değilse Atla | ZF = 0 |
| JG/JNLE | Büyükse/Küçük veya Eşit Değilse Atla (işaretli) | ZF = 0 and SF = OF |
| JGE/JNL | Büyük veya Eşitse/Küçük Değilse Atla (işaretli) | SF = OF |
| JL/JNGE | Küçükse/Büyük veya Eşit Değilse Atla (işaretli) | SF ≠ OF |
| JLE/JNG | Küçük veya Eşitse/Büyük Değilse Atla (işaretli) | ZF = 1 or SF ≠ OF |
**Sabitler**
Sabitlerin nasıl kullanılacağını gösteren bazı örneklere bakalım:
```assembly
SECTION_RODATA
constants_1: db 1,2,3,4
constants_2: times 2 dw 4,3,2,1
```
* SECTION_RODATA bunun salt okunur veri bölümü olduğunu belirtir. (Bu bir makrodur çünkü işletim sistemlerinin kullandığı farklı çıktı dosya formatları bunu farklı şekilde beyan eder)
* constants_1: constants_1 etiketi ```db``` (declare byte - byte beyan et) olarak tanımlanır - yani uint8_t constants_1[4] = {1, 2, 3, 4}; eşdeğeri
* constants_2: Bu, beyan edilen word'leri tekrarlamak için ```times 2``` makrosunu kullanır - yani uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1}; eşdeğeri
Derleyicinin bir bellek adresine dönüştürdüğü bu etiketler daha sonra yüklemelerde kullanılabilir (salt okunur oldukları için saklamalarda değil). Bazı talimatlar operand olarak bir bellek adresi alır, bu nedenle register'a açık yükleme olmadan kullanılabilirler (bunun artıları ve eksileri vardır).
**Offset'ler**
Offset'ler bellekteki ardışık öğeler arasındaki mesafedir (byte olarak). Offset, veri yapısındaki **her öğenin boyutu** ile belirlenir.
Artık döngü yazabildiğimize göre, veri almaya zaman. Ancak C ile karşılaştırıldığında bazı farklar var. Şu C döngüsüne bakalım:
```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```
Veri öğeleri arasındaki 4-byte offset C derleyicisi tarafından önceden hesaplanır. Ancak assembly'yi elle yazarken bu offset'leri kendiniz hesaplamanız gerekir.
Bellek adresi hesaplamaları için sözdizimini inceleyelim. Bu her türlü bellek adresi için geçerlidir:
```assembly
[base + scale*index + disp]
```
* base - Bu bir GPR'dır (genellikle bir C fonksiyon argümanından gelen pointer)
* scale - Bu 1, 2, 4, 8 olabilir. 1 varsayılandır
* index - Bu bir GPR'dır (genellikle bir döngü sayacı)
* disp - Bu bir integer'dır (32-bit'e kadar). Displacement veri içine bir offset'tir
x86asm, çalıştığınız SIMD register'ının boyutunu bilmenizi sağlayan mmsize sabitini sağlar.
Özel offset'lerden yüklemeyi göstermek için basit (ve anlamsız) bir örnek:
```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]
; bazı şeyler yap
add srcq, mmsize
dec r1q
jg .loop
RET
```
```movu m1, [srcq+2*r1q+3+mmsize]```'de derleyicinin kullanılacak doğru displacement sabitini önceden hesaplayacağına dikkat edin. Bir sonraki derste döngüde add ve dec yapmaktan kaçınma numarasını göstereceğiz, bunları tek bir add ile değiştireceğiz.
**LEA**
Artık offset'leri anladığınıza göre lea (Load Effective Address) kullanabilirsiniz. Bu, çarpma ve toplamayı tek talimat ile yapmanıza olanak tanır, bu da birden fazla talimat kullanmaktan daha hızlı olacaktır. Tabii ki çarpabileceğiniz ve toplayabileceğiniz şeylerde sınırlamalar vardır ancak bu lea'nın güçlü bir talimat olmasını engellemez.
```assembly
lea r0q, [base + scale*index + disp]
```
Adına rağmen, LEA normal aritmetik için ve adres hesaplamaları için kullanılabilir. Şu kadar karmaşık bir şey yapabilirsiniz:
```assembly
lea r0q, [r1q + 8*r2q + 5]
```
Bunun r1q ve r2q içeriklerini etkilemediğine dikkat edin. Ayrıca *FLAGS*'ı da etkilemez (bu nedenle çıktıya göre atlayamazsınız). LEA kullanmak tüm bu talimatları ve geçici register'ları önler (bu kod eşdeğer değildir çünkü add *FLAGS*'ı değiştirir):
```assembly
movq r0q, r1q
movq r3q, r2q
sal r3q, 3 ; shift arithmetic left 3 = * 8
add r3q, 5
add r0q, r3q
```
lea'nın döngülerden önce adresler ayarlamak veya yukarıdaki gibi hesaplamalar yapmak için çok kullanıldığını göreceksiniz. Tabii ki her türlü çarpma ve toplama yapamazsınız, ancak 1, 2, 4, 8 ile çarpmalar ve sabit bir offset toplamı yaygındır.
Ödevde bir sabit yüklemeniz ve değerleri bir döngüde bir SIMD vektörüne eklemeniz gerekecek.
[Sonraki Ders](../lesson_03/index.md)
================================================
FILE: lesson_02/index.zh.md
================================================
**FFmpeg 汇编语言第二课**
既然你已经写了你的第一个汇编语言函数,现在将介绍分支和循环。
首先介绍标签(label)和跳转的概念。在下面的人工示例中,jmp 指令将代码执行移动到 ".loop:" 之后。".loop:" 被称为*标签*(label),标签前的点表示它是一个*局部标签*(local label),允许你在多个函数中重用相同的标签名。当然,这个例子展示了一个无限循环,但稍后会将其扩展为更实际的内容。
```assembly
mov r0q, 3
.loop:
dec r0q
jmp .loop
```
在建立一个实际的循环之前,必须介绍 *FLAGS*(标志)寄存器。这里不会过多纠结于 *FLAGS* 的细节(因为 GPR 操作很大程度上只是脚手架),但有几个标志,如零标志(Zero-Flag)、符号标志(Sign-Flag)和溢出标志(Overflow-Flag),它们根据大多数非 mov 指令对标量数据(如算术运算和移位)的输出来设置。
这是一个例子,循环计数器递减直到零,jg(如果大于零则跳转)是循环条件。dec r0q 根据指令后 r0q 的值设置 FLAGS,你可以根据它们进行跳转。
```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 | 相等/为零时跳转 (Jump if Equal/Zero) | ZF = 1 |
| JNE/JNZ | 不相等/不为零时跳转 (Jump if Not Equal/Not Zero) | ZF = 0 |
| JG/JNLE | 大于/不小于等于时跳转 (有符号) (Jump if Greater/Not Less or Equal) | ZF = 0 且 SF = OF |
| JGE/JNL | 大于等于/不小于时跳转 (有符号) (Jump if Greater or Equal/Not Less) | SF = OF |
| JL/JNGE | 小于/不大于等于时跳转 (有符号) (Jump if Less/Not Greater or Equal) | SF ≠ OF |
| JLE/JNG | 小于等于/不大于时跳转 (有符号) (Jump if Less or Equal/Not Greater) | ZF = 1 或 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```(declare byte,即定义字节)- 相当于 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};
这些标签(汇编器会将其转换为内存地址)可用于加载(但不能用于存储,因为是只读的)。有些指令可以直接接受内存地址作为操作数,因此无需先显式加载到寄存器(这样做各有利弊)。
**偏移量(Offsets)**
偏移量是内存中连续元素之间的距离(以字节为单位)。偏移量由数据结构中**每个元素的大小**决定。
既然能够编写循环了,是时候获取数据了。但这与 C 相比有一些差异。来看下面的 C 循环:
```c
uint32_t data[3];
int i;
for(i = 0; i < 3; i++) {
data[i];
}
```
数据元素之间的 4 字节偏移量是由 C 编译器预先计算的。但是当手写汇编时,你需要自己计算这些偏移量。
来看内存地址计算的语法。这适用于所有类型的内存地址:
```assembly
[base + scale*index + disp]
```
* base - 这是一个 GPR(通常是来自 C 函数参数的指针)
* scale - 这可以是 1, 2, 4, 8。1 是默认值
* index - 这是一个 GPR(通常是循环计数器)
* disp - 这是一个整数(最高 32 位)。位移(Displacement)是进入数据的偏移量
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.zh.md)
================================================
FILE: lesson_03/index.es.md
================================================
**Lección Tres de Ensamblador con FFmpeg**
Vamos a explicar algo más de jerga y darte una breve lección de historia.
**Conjuntos de instrucciones**
Como viste en la lección anterior, hablamos de SSE2, que es un conjunto de instrucciones SIMD. Cuando se lanza una nueva generación de CPU, puede venir con nuevas instrucciones y, a veces, tamaños de registro más grandes. La historia del conjunto de instrucciones x86 es muy compleja, así que aquí tienes una versión simplificada (hay muchas más subcategorías):
* MMX - Lanzado en 1997, primer SIMD en procesadores Intel, registros de 64 bits, histórico
* SSE (Streaming SIMD Extensions) - Lanzado en 1999, registros de 128 bits
* SSE2 - Lanzado en 2000, muchas instrucciones nuevas
* SSE3 - Lanzado en 2004, primeras instrucciones horizontales
* SSSE3 (Supplemental SSE3) - Lanzado en 2006, nuevas instrucciones, pero lo más importante es la instrucción de barajado pshufb, probablemente la más importante en el procesamiento de vídeo
* SSE4 - Lanzado en 2008, muchas instrucciones nuevas incluyendo mínimo y máximo empaquetados
* AVX - Lanzado en 2011, registros de 256 bits (sólo float) y nueva sintaxis de tres operandos
* AVX2 - Lanzado en 2013, registros de 256 bits para instrucciones enteras
* AVX512 - Lanzado en 2017, registros de 512 bits, nueva funcionalidad de máscaras de operación. Tuvieron uso limitado en FFmpeg en su momento debido a la bajada de frecuencia del CPU al usar nuevas instrucciones. Barajado completo de 512 bits (permute) con vpermb
* AVX512ICL - Lanzado en 2019, sin reducción de frecuencia del reloj
* AVX10 - Próximamente
Vale la pena señalar que los conjuntos de instrucciones pueden eliminarse además de añadirse en las CPU. Por ejemplo, AVX512 fue [eliminado](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/) (de forma polémica) en las CPU Intel de 12.ª generación. Por esta razón, FFmpeg realiza detección de CPU en tiempo de ejecución. FFmpeg detecta las capacidades de la CPU en la que se está ejecutando.
Como viste en el ejercicio, los punteros a funciones son por defecto en C y se reemplazan por una variante del conjunto de instrucciones específico. Esto significa que la detección se hace una sola vez y no necesita repetirse. Esto contrasta con muchas aplicaciones propietarias que codifican un conjunto de instrucciones fijo, haciendo que un ordenador perfectamente funcional quede obsoleto. Esto también permite activar/desactivar funciones optimizadas en tiempo de ejecución. Esta es una de las grandes ventajas del software libre.
Programas como FFmpeg se usan en miles de millones de dispositivos en todo el mundo, algunos de los cuales pueden ser muy antiguos. Técnicamente, FFmpeg admite máquinas que solo soportan SSE, ¡que tienen 25 años! Por suerte, x86inc.asm puede avisarte si utilizas una instrucción no disponible en un conjunto concreto.
Para que tengas una idea de las capacidades reales, aquí está la disponibilidad de conjuntos de instrucciones según la [encuesta de Steam](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) en noviembre de 2024 (obviamente sesgada hacia jugadores):
| Conjunto de Instrucciones | Disponibilidad |
| :---- | :---- |
| SSE2 | 100% |
| SSE3 | 100% |
| SSSE3 | 99.86% |
| SSE4.1 | 99.80% |
| AVX | 97.39% |
| AVX2 | 94.44% |
| AVX512 (Steam no distingue entre AVX512 y AVX512ICL) | 14.09% |
Para una aplicación como FFmpeg con miles de millones de usuarios, incluso un 0,1% es un número muy grande de usuarios e informes de errores si algo falla. FFmpeg tiene una infraestructura de pruebas muy amplia para verificar las variaciones de CPU/SO/Compilador en nuestra [suite de pruebas FATE](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Cada commit se ejecuta en cientos de máquinas para asegurarse de que nada se rompe.
Intel proporciona un manual detallado de conjuntos de instrucciones aquí: [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
Puede ser tedioso buscar en un PDF, así que existe una alternativa web no oficial aquí: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/)
También hay una representación visual de instrucciones SIMD disponible aquí:
[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/)
Parte del reto del ensamblador x86 es encontrar la instrucción adecuada para lo que necesitas. En algunos casos, las instrucciones pueden usarse de formas distintas a las previstas originalmente.
**Trucos con desplazamientos de punteros**
Volvamos a nuestra función original de la Lección 1, pero ahora añadimos un argumento de *anchura* (width) a la función en C.
Usamos ptrdiff_t para la variable width en lugar de int para asegurarnos de que los 32 bits superiores del argumento de 64 bits están a cero. Si pasásemos directamente un int como ancho en la firma de la función y luego intentásemos usarlo como un quad para aritmética de punteros (es decir, usando `widthq`), los 32 bits superiores del registro podrían contener valores arbitrarios. Podríamos solucionar esto con `movsxd` para extender el signo (ver también el macro `movsxdifnidn` en x86inc.asm), pero esta forma es más sencilla.
La siguiente función contiene el truco de desplazamiento de punteros:
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width)
INIT_XMM sse2
cglobal add_values, 3, 3, 2, src, src2, width
add srcq, widthq
add src2q, widthq
neg widthq
.loop
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
paddb m0, m1
movu [srcq+widthq], m0
add widthq, mmsize
jl .loop
RET
```
Veámoslo paso a paso porque puede resultar confuso:
```assembly
add srcq, widthq
add src2q, widthq
neg widthq
```
La anchura se suma a cada puntero, de modo que ahora apuntan al final del búfer a procesar. Luego, width se niega.
```assembly
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
```
Las cargas se hacen con widthq siendo negativo. Así que en la primera iteración, [srcq+widthq] apunta a la dirección original de srcq, es decir, al inicio del búfer.
```assembly
add widthq, mmsize
jl .loop
```
Se suma mmsize a widthq (negativo), acercándolo a cero. La condición del bucle ahora es jl (saltar si menor que cero). Este truco hace que widthq se use como desplazamiento de puntero **y** como contador de bucle al mismo tiempo, ahorrando una instrucción cmp. También permite usar el desplazamiento en múltiples cargas y escrituras, e incluso en múltiplos del desplazamiento si es necesario (recuerda esto para el ejercicio).
**Alineación**
En todos nuestros ejemplos hemos usado movu para evitar el tema de la alineación. Muchas CPU pueden cargar y almacenar datos más rápido si los datos están alineados, es decir, si la dirección de memoria es divisible por el tamaño del registro SIMD. Cuando es posible, en FFmpeg intentamos usar cargas y escrituras alineadas con mova.
En FFmpeg, av_malloc puede proporcionar memoria alineada en el *heap*, y la directiva del preprocesador C DECLARE_ALIGNED puede hacerlo en la *stack*. Si se usa mova con una dirección no alineada, causará un fallo de segmentación y la aplicación se cerrará. También es importante asegurarse de que el valor de alineación corresponde al tamaño del registro SIMD: 16 con xmm, 32 con ymm y 64 con zmm.
Así es como se alinea el inicio de la sección RODATA a 64 bytes:
```assembly
SECTION_RODATA 64
```
Ten en cuenta que esto sólo alinea el comienzo de RODATA. Puede ser necesario añadir bytes de relleno para que la siguiente etiqueta siga alineada a 64 bytes.
**Expansión de rango**
Otro tema que hemos evitado hasta ahora es el desbordamiento. Esto ocurre, por ejemplo, cuando el valor de un byte supera 255 tras una operación como suma o multiplicación. Puede que queramos realizar una operación donde el valor intermedio sea mayor que un byte (por ejemplo, word), o incluso mantener los datos en ese tamaño mayor.
Para *bytes sin signo*, aquí entran punpcklbw (desempaqueta bytes bajos en words) y punpckhbw (desempaqueta bytes altos en words).
Veamos cómo funciona punpcklbw. La sintaxis para la versión SSE2 según el manual de Intel es:
| PUNPCKLBW xmm1, xmm2/m128 |
| :---- |
Esto significa que la fuente (lado derecho) puede ser un registro xmm o una dirección de memoria (m128 = dirección de memoria con la sintaxis estándar [base + scale*index + disp]), y el destino un registro xmm.
El sitio web de officedaytime.com tiene un buen diagrama que muestra lo que ocurre:

Puedes ver que los bytes se intercalan desde la mitad inferior de cada registro respectivamente. ¿Pero qué tiene que ver esto con la extensión de rango? Si el registro fuente está lleno de ceros, se intercalan los bytes del destino con ceros. Esto se conoce como *extensión con ceros*, ya que los bytes son sin signo. punpckhbw puede usarse para hacer lo mismo con los bytes altos.
Aquí tienes un fragmento que muestra cómo se hace:
```assembly
pxor m2, m2 ; poner m2 a cero
movu m0, [srcq]
movu m1, m0 ; copiar m0 en m1
punpcklbw m0, m2
punpckhbw m1, m2
```
```m0``` y ```m1``` ahora contienen los bytes originales extendidos con ceros a word. En la próxima lección verás cómo las instrucciones de tres operandos en AVX hacen innecesaria la segunda movu.
**Extensión de signo**
Los datos con signo son un poco más complejos. Para extender el rango de un entero con signo, necesitamos usar un proceso conocido como [extensión de signo](https://es.wikipedia.org/wiki/Sign_extension). Esto rellena los bits más significativos (MSB) con el bit de signo. Por ejemplo: -2 en int8_t es 0b11111110. Para extenderlo a int16_t, se repite el MSB (1), dando 0b1111111111111110.
```pcmpgtb``` (comparación empaquetada mayor que, por byte) puede usarse para extensión de signo. Al hacer la comparación (0 > byte), todos los bits del byte destino se ponen a 1 si el byte es negativo, y a 0 si no lo es. Luego punpckX puede usarse como antes para extender el signo. Si el byte es negativo, el correspondiente es 0b11111111, y si no, es 0x00000000. Intercalar el byte original con el resultado de pcmpgtb extiende el signo a word como resultado.
```assembly
pxor m2, m2 ; poner m2 a cero
movu m0, [srcq]
movu m1, m0 ; copiar m0 en m1
pcmpgtb m2, m0
punpcklbw m0, m2
punpckhbw m1, m2
```
Como puedes ver, hay una instrucción extra comparado con el caso sin signo.
**Empaquetado**
packuswb (empaquetar word sin signo a byte) y packsswb permiten ir de word a byte. Permiten intercalar dos registros SIMD que contienen word en un solo registro SIMD de byte. Ten en cuenta que si los valores superan el rango de byte, se saturan (es decir, se limitan al valor máximo).
**Shuffles (barajados)**
Los *shuffles*, también conocidos como permutaciones, son posiblemente la instrucción más importante en procesamiento de vídeo, y pshufb (barajado empaquetado de bytes), disponible en SSSE3, es su variante más importante.
Para cada byte, el byte fuente correspondiente se usa como índice del registro destino, excepto cuando el MSB está activado: entonces el byte destino se pone a cero. Es análogo al siguiente código en C (aunque en SIMD las 16 iteraciones ocurren en paralelo):
```c
uint8_t tmp[16];
memcpy(tmp, dst, 16);
for(int i = 0; i < 16; i++) {
if(src[i] & 0x80)
dst[i] = 0;
else
dst[i] = tmp[src[i]];
}
```
Aquí tienes un ejemplo simple en ensamblador:
```assembly
SECTION_DATA 64
shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1
section .text
movu m0, [srcq]
movu m1, [shuffle_mask]
pshufb m0, m1 ; baraja m0 según m1
```
Fíjate que se usa -1 para facilitar la lectura como índice de barajado que pone a cero el byte de salida: -1 como byte es el campo de bits 0b11111111 (complemento a dos), y por tanto el MSB (0x80) está activado.
[image1]: <data:image/png;base64,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>
================================================
FILE: lesson_03/index.fr.md
================================================
**FFmpeg Assembly Language Leçon Trois**
Expliquons un peu plus de jargon et lisez bien cette petite leçon d'histoire.
**Jeux d'instructions**
Vous avez sûrement prêté attention au fait que dans la leçon précédente, nous avons parlé de **SSE2** qui fait partie du jeu d'instructions **SIMD**. Quand une nouvelle génération de CPU sort sur le marché, elle peut être accompagnée de nouvelles instructions et quelquefois de registres de tailles plus grandes. L'histoire du jeu d'instruction x86 est tellement complexe qu'il en existe une version simplifiée (avec plusieurs sous-histoires dans celle-ci) :
* MMX - Lancée en 1997, la première version **SIMD** chez Intel Processors, registres de 64 bits, version historique.
* SSE (Streaming SIMD Extensions) - Lancée en 1999, registres de 128 bits.
* SSE2 - Lancée en 2000, plusieurs nouvelles instructions.
* SSE3 - Lancée en 2004, premières instructions *horizontales*.
* SSSE3 (Supplemental SSE3) - Lancée en 2006, ajout de nouvelles instructions dont la plus importante `pshufb`, sans doute l'instruction la plus importante pour le traitement vidéo.
* SSE4 - Lancée en 2008, ajout de nombreuses nouvelles instructions, notamment les minimums et maximums en mode vectorisé.
* AVX - Lancée en 2011, ajout de registres à 256 bits (uniquement pour les flottants) et d'une nouvelle syntaxe à trois opérandes.
* AVX2 - Lancée en 2013, ajout de registres à 256 bits pour les instructions entières.
* AVX512 - Lancée en 2017, registres à 512 bits, nouvelle instruction de masquage. Leur utilisation dans FFmpeg était très limitée en raison de la diminution de la fréquence du CPU quand de nouvelles instructions étaient utilisées. Permutation complète de 512 bits avec `vpermb`.
* AVX512ICL - Lancé en 2019, suppression de la réduction de fréquence du processeur.
* AVX10 - À venir.
Il est important de noter que des jeux d'instructions peuvent être supprimés ou ajoutés d'un CPU à l'autre. Par exemple, **AVX512** a été [supprimé](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/), de manière controversée, dans la 12ème génération de CPU Intel. C'est pour cette raison que FFmpeg fait de la détection du CPU à l'exécution. FFmpeg détecte les capacités du processeur sur lequel il s'exécute.
Comme vous l'avez vu dans l'exercice, les pointeurs de fonctions sont par défaut en **C** et sont remplacés par un jeu d'instruction particulier. Cela signifie que la détection est réalisée une fois et ne sera plus jamais requise. Cela contraste avec beaucoup d'applications propriétaires qui programment en dur dans leur code un jeu d'instruction, rendant obsolètes des ordinateurs parfaitement fonctionnels. Cela permet aussi d'activer ou de désactiver des fonctions optimisées à l'exécution. C'est l'un des plus grands avantages de l'open source.
Des programmes comme FFmpeg sont utilisés par des milliards d'appareils autour du monde, certains d'entre eux sont peut-être très âgés. Techniquement, FFmpeg est compatible avec des machines possédant uniquement le jeu d'instruction **SSE**, machines qui datent d'il y a 25 ans.
Heureusement, **x86inc.ams** est capable de vous indiquer si une instruction n'est pas disponible dans un jeu d'instruction particulier.
Pour vous donner une idée des compatibilités, voici la disponibilité des jeux d'instructions selon le [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) en novembre 2024 (évidemment biaisé en faveur des joueurs) :
| Jeu d'instruction | Disponibilité |
| :---- | :---- |
| SSE2 | 100% |
| SSE3 | 100% |
| SSSE3 | 99.86% |
| SSE4.1 | 99.80% |
| AVX | 97.39% |
| AVX2 | 94.44% |
| AVX512 (AVX512 et AVX512ICL confondus) | 14.09% |
Pour une application comme FFmpeg avec des milliards d'utilisateurs, même 0.1% d'entre eux représente un grand nombre d'utilisateurs et de rapports de bug en cas de problème. FFmpeg possède une grande infrastructure de tests pour tester chaque combinaison de CPU/OS/compilateurs présentée sur [FATE testsuite](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Chaque simple *commit* est exécuté sur des centaines de machines pour être certain que rien ne casse.
Intel fournit un manuel de jeu d'instructions détaillé ici : [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
Cela peut être laborieux de chercher sur un PDF, une alternative en ligne est présente ici : [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/)
Il y a aussi une représentation visuelle des instructions SIMD disponible ici : [https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/)
Une partie du défi de l'utilisation de l'assembleur x86 est de trouver la bonne instruction dont vous avez besoin. Dans certains cas, des instructions peuvent être utilisées dans des cas de figure pour lesquelles elles n'ont pas été faites originellement.
**Astuces sur les décalages de pointeurs**
Revenons à notre fonction originale de la Leçon 1, mais ajoutons un argument de largeur à la fonction en C.
Nous utilisons `ptrdiff_t` pour la variable de largeur au lieu de `int` afin de garantir que les 32 bits supérieurs de l’argument 64 bits sont mis à zéro. Si nous passions directement une largeur en `int` dans la signature de la fonction, puis tentions de l’utiliser comme un `quad` pour l’arithmétique des pointeurs (c’est-à-dire en utilisant `widthq`), les 32 bits supérieurs du registre pourraient contenir des valeurs arbitraires. Nous pourrions corriger cela en étendant le signe de `width` avec `movsxd` (voir aussi la macro `movsxdifnidn` dans **x86inc.asm)**, mais cette méthode est plus simple.
La fonction ci-dessous contient l’astuce des décalages de pointeur :
```assembly
;static void add_values(const uint8_t *src, const uint8_t *src2, ptrdiff_t width)
INIT_XMM sse2
cglobal add_values, 3, 3, 2, src, src2, width
add srcq, widthq
add src2q, widthq
neg widthq
.loop
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
paddb m0, m1
movu [srcq+widthq], m0
add widthq, mmsize
jl .loop
RET
```
Passons en revue cela étape par étape, car cela peut être déroutant :
```assembly
add srcq, widthq
add src2q, widthq
neg widthq
```
La largeur `widthq` est ajoutée à chaque pointeur de sorte que chaque pointeur pointe maintenant vers la fin du tampon à traiter. Le signe de `widthq` est ensuite inversé.
```assembly
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
```
Les chargements sont ensuite effectués avec `widthq` étant négatif. Ainsi, lors de la première itération, `[srcq+widthq]` pointe vers l'adresse originale de `srcq`, c'est-à-dire qu'il pointe vers le début du tampon.
```assembly
add widthq, mmsize
jl .loop
```
`mmsize` est ajouté à `widthq` négatif, le rapprochant ainsi de zéro. La condition de boucle est maintenant `jl` (*jump if less than zero*). Cette astuce permet à `widthq` d’être utilisé à la fois comme décalage de pointeur et comme compteur de boucle, économisant ainsi une instruction `cmp`. Elle permet également d’utiliser le décalage du pointeur dans plusieurs chargements et stockages, ainsi que d'utiliser des multiples des décalages de pointeur si nécessaire (retenez cela pour l'exercice).
**Alignement**
Dans tous nos exemples, nous avons utilisé `movu` pour éviter le sujet de l’alignement. De nombreux processeurs peuvent charger et stocker des données plus rapidement si celles-ci sont alignées, c’est-à-dire si l’adresse mémoire est divisible par la taille du registre SIMD. Lorsque c’est possible, nous essayons d’utiliser des chargements et des stockages alignés dans FFmpeg en utilisant `mova`.
Dans FFmpeg, `av_malloc` peut fournir une mémoire alignée sur le tas, et la directive de préprocesseur C `DECLARE_ALIGNED` peut fournir une mémoire alignée sur la pile. Si `mova` est utilisé avec une adresse non alignée, cela entraînera une faute de segmentation et l’application plantera. Il est également important de s’assurer que la valeur d’alignement correspond à la taille du registre SIMD, c’est-à-dire 16 pour `xmm`, 32 pour `ymm` et 64 pour `zmm`.
Voici comment aligner le début de la section RODATA sur 64 octets :
```assembly
SECTION_RODATA 64
```
Notez que cela aligne uniquement le début de la section RODATA. Des octets de remplissage peuvent être nécessaires pour s'assurer que le label suivant reste sur une limite de 64 octets.
**Décompactage**
Un autre sujet que nous avons évité jusqu'à présent est le débordement. Cela se produit, par exemple, lorsque la valeur d'un octet dépasse 255 après une opération comme l'addition ou la multiplication. Nous pouvons vouloir effectuer une opération où nous avons besoin d'une valeur intermédiaire plus grande qu'un octet (par exemple, des mots), ou potentiellement nous voulons laisser les données dans cette taille intermédiaire plus grande.
Pour les octets non signés, c'est là que les instructions `punpcklbw` (décompacter des octets en mots dans le bas d'un paquet) et `punpckhbw` (décompacter des octets en mots dans le haut d'un paquet) interviennent.
Voyons comment fonctionne `punpcklbw`. La syntaxe pour la version SSE2 dans le manuel Intel est la suivante :
| PUNPCKLBW xmm1, xmm2/m128 |
| :---- |
Cela signifie que sa source (côté droit) peut être un registre `xmm` ou une adresse mémoire (`m128` signifie une adresse mémoire avec la syntaxe standard **[base + échelle\*index + déplacement]**), et la destination est un registre `xmm`.
Le site web officedaytime.com ci-dessus a un bon diagramme montrant ce qui se passe :

Vous pouvez voir que les octets sont entrelacés à partir de la moitié inférieure de chaque registre respectivement. Mais quel rapport cela a-t-il avec l'extension de plage ? Si le registre source est entièrement nul, cela entrelace les octets dans le registre de destination avec des zéros. C'est ce qu'on appelle l'*extension par zéro*, car les octets sont sans signe. `punpckhbw` peut être utilisé pour faire la même chose pour les octets supérieurs.
Voici un extrait montrant comment cela est fait :
```assembly
pxor m2, m2 ; zero out m2
movu m0, [srcq]
movu m1, m0 ; make a copy of m0 in m1
punpcklbw m0, m2
punpckhbw m1, m2
```
`m0` et `m1` contiennent maintenant les octets d'origine étendus à zéro en mots. Dans la prochaine leçon, vous verrez comment les instructions à trois opérandes en AVX rendent le deuxième `movu` inutile.
**Extension de signe**
Les données signées sont un peu plus compliquées. Pour étendre la plage d'un entier signé, nous devons utiliser un processus connu sous le nom d'[extension de signe](https://en.wikipedia.org/wiki/Sign_extension). Cela consiste à ajouter des bits de signe dans les bits les plus significatifs. Par exemple : `-2` en `int8_t` est `0b11111110`. Pour l'étendre à `int16_t`, le bit de signe `1` est répété pour obtenir `0b1111111111111110`.
`pcmpgtb` (*packed compare greater than byte*) peut être utilisé pour l'extension de signe. En effectuant la comparaison (0 \> octet), tous les bits dans l'octet de destination sont définis sur 1 si l'octet est négatif, sinon les bits dans l'octet de destination sont définis sur 0. `punpckX` peut être utilisé comme ci-dessus pour effectuer l'extension de signe. Si l'octet est négatif, l'octet correspondant est `0b11111111` et sinon il est `0x00000000`. L'entrelacement de la valeur de l'octet avec la sortie de `pcmpgtb` effectue une extension de signe en mot.
```assembly
pxor m2, m2 ; zero out m2
movu m0, [srcq]
movu m1, m0 ; make a copy of m0 in m1
pcmpgtb m2, m0
punpcklbw m0, m2
punpckhbw m1, m2
```
Comme vous pouvez le voir, il y a une instruction supplémentaire par rapport au cas non signé.
**Packing**
`packuswb` (*pack unsigned word to byte*) et `packsswb` vous permettent de passer du mot au byte. Ils vous permettent d'entrelacer deux registres SIMD contenant des mots en un seul registre SIMD avec des bytes. Notez que si les valeurs dépassent la plage des bytes, elles seront saturées (c'est-à-dire *clampées* à la valeur maximale).
**Shuffles**
Les mélanges (*shuffles*), également appelés permutations, sont sans doute l'instruction la plus importante dans le traitement vidéo et `pshufb` (*packed shuffle bytes*), disponible dans SSSE3, est la variante la plus importante.
Pour chaque byte, le byte source correspondant est utilisé comme un index dans le registre de destination, sauf lorsque le bit de poids fort (MSB) est activé, le byte de destination est mis à zéro. C'est l'analogue du code C suivant (bien que dans SIMD, les 16 itérations de boucle se produisent en parallèle) :
```c
uint8_t tmp[16];
memcpy(tmp, dst, 16);
for(int i = 0; i < 16; i++) {
if(src[i] & 0x80)
dst[i] = 0;
else
dst[i] = tmp[src[i]];
}
```
Voici un exemple simple en assembleur:
```assembly
SECTION_DATA 64
shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1
section .text
movu m0, [srcq]
movu m1, [shuffle_mask]
pshufb m0, m1 ; shuffle m0 based on m1
```
Notez que -1, pour une lecture facile, est utilisé comme l'indice de mélange pour mettre à zéro le byte de sortie : `-1` en tant que byte est le champ binaire `0b11111111` (complément à deux), et ainsi le bit de poids fort (MSB) (`0x80`) est activé.
================================================
FILE: lesson_03/index.md
================================================
**FFmpeg Assembly Lesson Three**
Let’s explain some more jargon and give you a short history lesson.
**Instruction Sets**
You may have seen in the previous lesson we talked about SSE2 which is a set of SIMD instructions. When a new CPU generation is released it may come with new instructions and sometimes larger register sizes. The history of the x86 instruction set is very complex so this is a simplified history (there are many more subcategories):
* MMX - Launched in 1997, first SIMD in Intel Processors, 64-bit registers, historic
* SSE (Streaming SIMD Extensions) - Launched in 1999, 128-bit registers
* SSE2 - Launched in 2000, many new instructions
* SSE3 - Launched in 2004, first horizontal instructions
* SSSE3 (Supplemental SSE3) - Launched in 2006, new instructions but most importantly pshufb shuffle instruction, arguably the most important instruction in video processing
* SSE4 - Launched in 2008, many new instructions including packed minimum and maximum.
* AVX - Launched in 2011, 256-bit registers (float only) and new three-operand syntax
* AVX2 - Launched in 2013, 256-bit registers for integer instructions
* AVX512 - Launched in 2017, 512-bit registers, new operation mask feature. These had limited use at the time in FFmpeg because of CPU frequency downscaling when new instructions were used. Full 512-bit shuffle (permute) with vpermb.
* AVX512ICL - Launched 2019, no more clock frequency downscaling.
* AVX10 - Upcoming
It’s worth noting that instruction sets can be removed as well as added to CPUs. For example AVX512 was [removed](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/), controversially, in 12th Generation Intel CPUs. It’s for this reason that FFmpeg does runtime CPU detection. FFmpeg detects the capabilities of the CPU it’s running on.
As you saw in the assignment, function pointers are C by default and are replaced with a particular instruction set variant. This means detection is done once and then never needs to be done again. This is in contrast to many proprietary applications which hardcode a particular instruction set making a perfectly functional computer obsolete. This also allows optimised functions to be turned on/off at runtime. This is one of the big benefits of open source.
Programs like FFmpeg are used on billions of devices around the world, some of which may be very old. FFmpeg technically supports machines supporting SSE only, which are 25 years old! Thankfully x86inc.asm is capable of telling you if you use an instruction that’s not available in a particular instruction set.
To give you an idea of real-world capabilities, here is the instruction set availability from the [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) as of November 2024 (this is obviously biased towards gamers):
| Instruction Set | Availability |
| :---- | :---- |
| SSE2 | 100% |
| SSE3 | 100% |
| SSSE3 | 99.86% |
| SSE4.1 | 99.80% |
| AVX | 97.39% |
| AVX2 | 94.44% |
| AVX512 (Steam does not separate between AVX512 and AVX512ICL) | 14.09% |
For an application like FFmpeg with billions of users, even 0.1% is a very large number of users and bug reports if something breaks. FFmpeg has extensive testing infrastructure for testing the variations of CPU/OS/Compiler in our [FATE testsuite](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Every single commit is run on hundreds of machines to make sure nothing breaks.
Intel provides a detailed instruction set manual here: [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
It can be cumbersome to search through a PDF so there is an unofficial web based alternative here: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/)
There is also a visual representation of SIMD instructions available here:
[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/)
Part of the challenge of x86 assembly is finding the right instruction for your needs. In some cases instructions can be used in a way they were not originally intended.
**Pointer offset trickery**
Let’s go back to our original function from Lesson 1, but add a width argument to the C function.
We use ptrdiff_t for the width variable instead of int to make sure that the upper 32-bits of the 64-bit argument are zero. If we directly passed an int width in the function signature, and then attempted to use it as a quad for pointer arithmetic (i.e. using `widthq`) the upper 32-bits of the register can be filled with arbitrary values. We could fix this by sign extending width with `movsxd` (also see macro `movsxdifnidn` in x86inc.asm), but this is an easier way.
The function below has the pointer offset trickery in it:
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width)
INIT_XMM sse2
cglobal add_values, 3, 3, 2, src, src2, width
add srcq, widthq
add src2q, widthq
neg widthq
.loop
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
paddb m0, m1
movu [srcq+widthq], m0
add widthq, mmsize
jl .loop
RET
```
Let’s go through this step by step as it can be confusing:
```assembly
add srcq, widthq
add src2q, widthq
neg widthq
```
The width is added to each pointer such that each pointer now points to the end of the buffer to be processed. The width is then negated.
```assembly
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
```
The loads are then done with widthq being negative. So on the first iteration [srcq+widthq] points to the original address of srcq, i.e points back to the beginning of the buffer.
```assembly
add widthq, mmsize
jl .loop
```
mmsize is added to the negative widthq bringing it closer to zero. The loop condition is now jl (jump if less than zero). This trick means widthq is used as a pointer offset **and** as a loop counter at the same time, saving a cmp instruction. It also allows the pointer offset to be used in multiple loads and stores, as well as using multiples of the pointer offsets if needed (remember this for the assignment).
**Alignment**
In all our examples we have been using movu to avoid the topic of alignment. Many CPUs can load and store data faster if the data is aligned, i.e if the memory address is divisible by the SIMD register size. Where possible we try to use aligned loads and stores in FFmpeg using mova.
In FFmpeg, av_malloc is able to provide aligned memory on the heap and the DECLARE_ALIGNED C preprocessor directive can provide aligned memory on the stack. If mova is used with an unaligned address, it will cause a segmentation fault and the application will crash. It’s also important to be sure that the alignment value corresponds to the SIMD register size, i.e 16 with xmm, 32 for ymm and 64 for zmm.
Here is how to align the beginning of the RODATA section to 64-bytes:
```assembly
SECTION_RODATA 64
```
Note that this just aligns the beginning of RODATA. Padding bytes might be needed to make sure the next label remains on a 64-byte boundary.
**Range expansion**
Another topic we have avoided until now is overflowing. This happens, for example, when the value of a byte goes beyond 255 after an operation like addition or multiplication. We may want to perform an operation where we need an intermediate value larger than a byte (e.g words), or potentially we want to leave the data in that larger intermediate size.
For unsigned bytes, this is where punpcklbw (packed unpack low bytes to words) and punpckhbw (packed unpack high bytes to words) comes in.
Let’s look at how punpcklbw works. The syntax for the SSE2 version from the Intel Manual is as follows:
| PUNPCKLBW xmm1, xmm2/m128 |
| :---- |
This means its source (right hand side) can be an xmm register or a memory address (m128 means a memory address with the standard [base + scale*index + disp]) syntax and the destination an xmm register.
The officedaytime.com website above has a good diagram showing what’s going on:

You can see that bytes are interleaved from the lower half of each register respectively. But what has this got to do with range extension? If the src register is all zeros this interleaves the bytes in dst with zeros. This is what is known as *zero extension* as the bytes are unsigned. punpckhbw can be used to do the same thing for the high bytes.
Here is a snippet showing how this is done:
```assembly
pxor m2, m2 ; zero out m2
movu m0, [srcq]
movu m1, m0 ; make a copy of m0 in m1
punpcklbw m0, m2
punpckhbw m1, m2
```
```m0``` and ```m1``` now contain the original bytes zero extended to words. In the next lesson you’ll see how three-operand instructions in AVX make the second movu unnecessary.
**Sign extension**
Signed data is a bit more complicated. To range extend a signed integer, we need to use a process known as [sign extension](https://en.wikipedia.org/wiki/Sign_extension). This pads the MSBs with the sign bit. For example: -2 in int8_t is 0b11111110. To sign extend it to int16_t the MSB of 1 is repeated to make 0b1111111111111110.
```pcmpgtb``` (packed compare greater than byte) can be used for sign extension. By doing the comparison (0 > byte), all the bits in the destination byte are set to 1 if the byte is negative, otherwise the bits in the destination byte are set to 0. punpckX can be used as above to perform the sign extension. If the byte is negative the corresponding byte is 0b11111111 and otherwise it’s 0x00000000. Interleaving the byte value with the output of pcmpgtb performs a sign extension to word as a result.
```assembly
pxor m2, m2 ; zero out m2
movu m0, [srcq]
movu m1, m0 ; make a copy of m0 in m1
pcmpgtb m2, m0
punpcklbw m0, m2
punpckhbw m1, m2
```
As you can see there is an extra instruction compared to the unsigned case.
**Packing**
packuswb (pack unsigned word to byte) and packsswb lets you go from word to byte. It lets you interleave two SIMD registers containing words into one SIMD register with a byte. Note that if the values exceed the byte range, they will be saturated (i.e clamped at the largest value).
**Shuffles**
Shuffles, also known as permutes, are arguably the most important instruction in video processing and pshufb (packed shuffle bytes), available in SSSE3, is the most important variant.
For each byte the corresponding source byte is used as an index of the destination register, except when the MSB is set the destination byte is zeroed. It’s analogous to the following C code (although in SIMD all 16 loop iterations happen in parallel):
```c
uint8_t tmp[16];
memcpy(tmp, dst, 16);
for(int i = 0; i < 16; i++) {
if(src[i] & 0x80)
dst[i] = 0;
else
dst[i] = tmp[src[i]];
}
```
Here’s a simple assembly example:
```assembly
SECTION_DATA 64
shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1
section .text
movu m0, [srcq]
movu m1, [shuffle_mask]
pshufb m0, m1 ; shuffle m0 based on m1
```
Note that -1 for easy reading is used as the shuffle index to zero out the output byte: -1 as a byte is the 0b11111111 bitfield (two’s complement), and thus the MSB (0x80) is set.
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAc0AAAC0CAIAAAB5feD3AAAjwklEQVR4Xu2djY8d1XnG91/CQpazKERR2uarVUEVrSOTkCXqWlWcD2gaubVpi4mo7YCzJYQUh+2K2g0UXCzFsEYYG+PCxriOXWyCBXsdBzmJgYAJY4S1ItH0nTl3zj2f75w7Z2b33LvPT6/su2fmPve95+OZM3PvnDuRAwAA6JIJswAAAECrwGcBAKBb4LMAANAt8FkAAOgW+CwAAHQLfBYAALoFPgsAAN0CnwUArBYWZyYnZxbN0u6BzwIAVgvwWQAA6Bb4bGrMT09Mz5uPiwczM5MTJVWDOQuLFhUllUzRxNPTReGKtDQAQPNZGrgVYowqW4ttysAtSmNGNHzWh89nVXsVD12FeouVTy6bSUoCAJadwbgshmM1ROVjOaYXZ6Yn+3sWG2nYxo1o+KwPn88O6rX6w1E4OPSVmNYLAFgJBoNQG7XSYPultNv0/Py0UhQ5ouGzPqJ91jzQDdEqAIAuYHxWHeHz08Vf5b8D540Z0fBZH/PyAo1yelAUWucdrkJHswzRKgCALhgMwsH41R8XvjotZrLFrHammNlW+zQf0fBZP4V/inOEGfVoJz/yqmrdWSjcuRIoGmOIVgEAdIE2CKsBrg5ba9rUzoiGzw6FfrLBFQIAQB/47FA4LdVZCAAAfeCzAADQLfBZAADoFvgsAAB0C3xW46cv/+LG2x5c98WdMXHdLffYhcNGIiLxCuuSEYlXWJeMSLzCuvESiVdY15IIGciTx84axgKf1fj8ph/YFYdAIBDhcf2tuwxjgc9q2FWGQCAQw4ZhLPBZDV81hROvkCcjEq+QJyMSr5ArIllvsXHEi8QrZOMlEq+QtS1i9Bz4rIavmsKJV8iTEYlXyJMRiVfI4bNWJCISr5C1LWL0HPishq+awolXyJMRiVfIkxGJV8jhs1YkIhKvkLUtYvQc+KyGr5rCiVfIkxGJV8iTEYlXyOGzViQiEq+QtS1i9Bz4rIavmsKJV8iTEYlXyJMRiVfI4bNWJCISr5C1LWL0HPishq+awolXyJMRiVfIkxGJV8jhs1YkIhKvkLUtYvQc+KyGr5rCiVfIkxGJV8iTEYlXyOGzViQiEq+QtS1i9JzV4LNi3ciglSLd1VSuU6ksyaUsRKlvyEMV+gghuzxQxFoQUyNQZLAKp6XhVshdIr5Cn4i9s7IYqJWIR0QyeK791D7BCn4J3mdn10uFkvX77H30oWhvErFvoOQWYRWe3b5GzeIha4cQkX6c3noNaUzNmuUyeBHx9JJrth82twaJyFpds+W0vTVEQdFh3khWK6K079qtz5pbLRGj54y7z4qV0udDF+S1q6kYgMVPBZk+axuKIEyhpFwmWC4hrhIm0v/9otyT0JAioqa0SrIVcreIu1Bgi7h2nq9+AM+ZiENkgPrmhbS+XcApmK/uVsh5n1WDxmRTgytM1v9cEaxC4bO8oYhgRco4vGXtmi3b13NqrMhDU5U5Fobrf1N+EalQvKkQd7M3ZVWV7mPfSJ3IQ1PyUEHV4j9sSBGj56Tms8oS5dWYqeyoZLr8wZ7qYfUE79Y+TgNw4asmXYAbi2EKuXynVnlBsEgfxScGDCuiVH0fn0LuEXEW+kS0nfVn2m/HJ5KbjWE/tQ+j0LbP1jgdK0Lj2T2HDVaoeXUZrMhiqVO4CW9PdSL9IJHGLimCnDpSgX8jGS+iHziZtyNFjJ6Tms9W/Vzp7qVzlo+LQuVh+YDfKvAOPgtfNdk+KzEGZZjCwNSae5Oahy0RLFLhMBifQu4RcRb6RNSdjdceyvHLHuBTGsAp5HVVWSFF7AE2CBqTjU9yiynkNWurruWbA3IK+nUDxllYkYGv8fZUK8K/kRCRMmoOHgEKNW8kY0WM+Thj+lLE6DnJ+WzfOQfGqIwc3XzLPfitAu/Ys/FVk9NBCrRBXhCkoKdqKweJKBR61pFkKBFnHfkUco+Is9An0prPVj2mwqqIEl6htNnpeeG29nuokCL2AKuixhEydjyX56RyPktzW7cUp6BGcUnROzvmRJQ0eHviRJQofMp/7KkV4S87ZAEKWd0byViRcfTZ/qCRnX0wHl2eym8V6K7L4qsmp4OUmOJBCroxTFjeECSiYaaRDyPitOncr5C7RHyFPhHGZ+034xOxcKZQwClor+c84vSRIvYA6wdrbSI4Ec1nvdbAKWihXFW0ghMxP9OrP022N+nRNJM6jw5REOGrTBmMiOGzo3/doOrkg56vjAH9YWWzzFaBPTvy46sm7/CdbzSfVXCWB4mof1hp5IEi7qf28SnklghT6BPRdlad3uX6PhGDZu9F7TqsRr3PMiNQBiuiTIf9n7ewCkqwph8owtsTJ6Je02yaScinghmrIIN/IxkvoraFv11UEaPnpOSz6gCrertiko6HjiL1YSEywDd4VOxq0jVsXVM1TGFAoDe5RIr6qrA1wkRUjQJNx1bInSKeQoEt4t5ZKbXfjC0yoKYa+nAKekpmCylIEXuAVSPQ6yYyAkT6+HyBVSiuNlRwybAig+DtiRVRrxQ3ykSpCl7Eq1BGYdYD6i3S3lSEMscPqRCj56Tkswngq6Zw4hXyZETiFfJkROIV8lqfDYt4kXiFbLxE4hWytkWMngOf1fBVUzjxCnkyIvEKeTIi8Qo5fNaKRETiFbK2RYyeA5/V8FVTOPEKeTIi8Qp5MiLxCjl81opEROIVsrZFjJ4Dn9XwVVM48Qp5MiLxCnkyIvEKOXzWikRE4hWytkWMngOf1fBVUzjxCnkyIvEKeTIi8Qo5fNaKRETiFbK2RYyeA5/VkNWEQCAQjcMwFvisxvW37rKrDIFAIIz45Nf22IUyDGOBz2o8fOC4XWUIBAJhxOf+8ZBdKGLH3DOGscBnAQBgaMhnzSI/o+6zS7Ob393Yj/cPvm1uBmA5WfroD48du3Dy9XfMDWClefO9D89cuGyWRrCqfFbhlSsbZ65eMksBWCaOnrm0YefzOx4/S0Pa3AZWmv0Lb+w++JpZGsHq8tlLR96v5rPvwmfBikATpU0PHN88d/L8pczcBtLgrkdeXnj1LbM0gtXks29f3bb5ymn5GD4LlpeLv/3gjj2npu9baHcMg9a56e7nPrj6kVkawWryWeVawelHMZ8Fy8flK0v3Hzi3YefzT524aG4DiUEnHHS2YZbGsZp8VtiruGjw6JVZ+CzonqWP/rD3yHmaH9G/7U6RQEdQS1GYpXGsLp8FYDmh2SvNYWkmS/NZcxtIFZrMtvtlgxw+C0AXnHz9nen7Fu7Ycwofdo0WdP5BJx/0r7khDvgsAG1CxkoTok0PHG99TgSWgS4uzubwWQDa4vKVpR2Pn92w8/lDp35tbgMjQhcXZ3P4LADxfHD1o7lDi+SwNERbP+UEy0kXF2dz+CwAkexfeOOmu5/bffA1fNg16tDxsouLszl8FoDGLLz61tSuF+565OWLv/3A3AZGEGpQak2ztA3gswAMzbmLv7t99wmKLs4xwUpBJyV0dmKWtgF8FoAhePO9D2nKQ9PYo2dwm8u4semB4x19Dw8+C0AQl68s0XznprufoylPF5fwwMoiLs6apS0BnwWgBrFQ7Iadz5PP4t7ZcaW7i7M5fBYAnkOnfo2FYlcD9x8419HF2Rw+C4APuVDsuYu/M7eBsWNq1wvdHUrhs8356cu/uPG2B+0fVhsqrrvlHrtw2EhEJF5hXRoi133l+3/y7Sc+/fdPfnxjVPtGpiEiXiReYd14idgKk1Mzn/mHp+w9mbBFmPic53cYyUCePHbWMBb4rMbnN/3ArjjESMfkl7/3qdse+cyW+U98dc7eihjX+MTfPPSp2//LLm8rfD5Lcf2tuwxjgc9q2FWGGN342C33fvJrez679Wn6lx7bOyDGOMhkyWrt8raC8VkKw1jgsxq+agonXiFPRiReIV85EbFQ7K79Pxf3zjZQsJEivd9kjSNeJF6hN14iToUvfvfYS6/91t7ZF04RJshn7UIpYvQc+KyGr5rCiVfIkxGJV8hXQmTh1bfshWKHUvAhRewBFh7xIvEKvfESsRXIYcln7T2ZsEX4gM82x1dN4cQr5MmIxCvkyysiF4o9+fo7xqZABR4pYg+w8IgXiVfojZeIrfCfR87f+eP/s/dkwhbhAz7bHF81hROvkCcjEq+QL5fIm+99yC8UW6sQghSxB1h4xIvEK/TGS8RW2Dz3syde/KW9JxO2CB/w2eb4qimceIU8GZF4hbx7kcCFYhmFcKSIPcDCI14kXqE3XiK2wl9858jZX75n78mELcLHKPvs4szkxPS8Wbp8+KopnHiFPBmReIW8SxFy1fCFYp0KwyJF7AEWHvEi8Qq98RIxFJ4/e+mv/3XB3o2PYdNI3Wfnpyf6TM4sWpvCXLbwY+P5TlmxnyBE2V1NpbTydFXV1A1T6COE7PJAESUPqyqDRQYVZ2m4FXKXiK/QJ2LvPGg/OxGHiLZQrLPtdWwFDfbVJVLEHmC9vVNSoWRq1t5HH4r2JhGzN9eIsAqnt16rZHHzPmuHEJF+HL5zLWms32uWy+BFxNNL1m590dwaJCJr9drth+2tHoUfPf36d/edtXWYN2KLmKG075o7T/dS99nFmZn+6Cq6tumUTB+XFO4yOTM/M6nu7JRVBRf1/T3Y1VTITc/PawcAbtYdplBSpjRjl4eKzE9X78iZ0JAiolq1GrIVcreIu1Bgi7h2VprKkYgmcubCZW2hWPXNC2nliRI7DQXj1d0KOe+zxphsanCFyfqfK4JVKHyWNxQRrEgZL25fc+32rTdzaqzIvvWVORaG639TfhGpULwp4W7OMBSMi7OiSmfZN2KL6LFvvTxUULWUj9v1WcWfqk5c+UMJFRTl/YfVE7xbFYwxMejgQQrOMV2gyCr7hNmsdzTqr8aNxTCFXGZklRcEi/RRfGLAsCJ2FfkUco+Is9Anou2sP9N+O0JhcmrGXihWbwz7qX18aZQoz+LaNtBna5yOFaHx7J7DBivUvLoMViQrdQo34e2pTqQfJBLuks4gpw5XcF6c5d+ILaKFfuAUb6ddn606ntL/CicTj4tC5WH5gN8qKUrUEaGMtBAF33jSZcXzC5w72/iqSfeBgWyV3YAwhYGpNfcmNQ9bIlikwm4ir0LuEXEW+kTUnY3XlpUjKe6d/eaPP7v1acdCsUV38SkN8KXRh6/KCiliD7BB0Jgc5iRXi2IKuXZN1bV8c0BOQb9uwDgLKzLwNd6eakX4NxIiUkbNwUNVePb0b5wXZ/k3YogYYczHReW07LN939POwKvOqDysjI/f2kd3Q2N7iIJ7OBmy5dihvwe+XYuvmpwOUqAN8oIgBf192cpBIgpWfRYMJeKsUJ9C7hFxFvpEAn1WLBT7mS3z5LMfu+VeuY+K6KAVVkWU+NIQlF1ler78z/EeKqSIPcCqqHGEHjuey3NSOZ+lua1bilNQo7ik6J0dcyJKGrw9cSJKFD7lP/bUivCXHXq6wvd/co7C3od/I4aIEcvis/1eLHvfYIC4HJHf2n9sdGV9mNUqiH2M4WTJ+oyNxVdNTgcpMTMJUtCNYcLyhiARDTONfBgRp03nfoXcJeIr9ImoO/taVy4UOzk14xSxcKZQ4EujQKs8rqtIEXuA9YO1NhGciOazXmvgFLRQripawYmYn+l5z/o5ES2aZlLn0bbCNx586emTv7L38VWmU8QIw2c7uG5Q9bpBV1Q6pf6w7Jz81uKB3Yn1sVGjULJonFg6ZIPHjoavmrzD13rlYRWc5UEi6h9WGnmgiPupfXwKuSXCFPpEtJ1Vpy8ff/uotlCsT8Sg2XvR+wqjUe+z/IVIEayIMh2uPm+x9uEVlGBNP1CEtydORL2m2TSTkE8Fe4oC9Za/+M4R+tfeh38jqoi9SWuL9j8HU3t/1f0Ui3M8dBQNHhZyGmV31jq562nawyINQ8Atqxf7Bo6JXU36C9pJmMphCgMCvcklor5DWyNMxKw8TcdWyJ0inkKBLeLeWSld861T0/ctLLz6lnyKLTKgphr6cAp6SmYLKUgRe4BVI9DrJjICRPr4fIFVKK42VHDJsCKD4O2JFVGvFDfKRKkKXkQq0EyW5rPG1sKsB7gPXaqIvakIZY4vKqQ9n10GFC9NAV81hROvkCcjEq+QDyNy+crS/QfObdj5vP1bI+EiPuIV8lqfDYt4kXiF3niJSAXfxdmQGDaNEfLZYirin4KsAL5qCideIU9GJF4hDxP54OpHe4+cv+nu5+hf568ihojwxCvk8FkrEhGRCr6LsyExbBoj5LPJ4aumcOIV8mRE4hXyABFjoVgntSK1xCvk8FkrEhERT//YLff6Ls6GxLBpwGeb46umcOIV8mRE4hVyVsS5UKwTRiSQeIUcPmtFIiLi6R/f+ODmuZ/ZWwNj2DTgs83xVVM48Qp5MiLxCrlHhFko1olTZCjiFXL4rBWJiIinf+qbP/7R06/bWwNj2DTgs82R1YToKCanZv7obx8vfhWxy99uQqzC+PTmn1z3le/b5R3F51bT74MtzW6+ctosbM71t+6yqwzRShS/iviNveSwn/zannVfGuIHnBGI2qDe9dmtT9vl3QV8tjkPHzhuVxkiNr50zye+OkfDgM7sJr/8PXMrAhEdH9/44B9/a59d3l0wPrtj7hnDWOCzoFu0hWIB6IbdB1+zv3bdKeSzZpGfcfDZg0fe37j5XYptR35vbgcrh1godtMDx/sLxY479Db3Hjl/x55T5opioHum71vgD+SOld6Gh15CfjdmtflsZa9vX922+f2Db5t7gOXnzfc+tBeKHT8uX1mi2frcoUU6nNyw7fDmuZPks+S28eMZDAX1N+psZqnCrv0/b+X4Rw0tbwdfbT47uG5w+tF3Z19Rt4LlhqyHzuBuuvu5x45diO/WCUIzmkOnfk3jliZQYi0xmiiJxW7ASiFaxCwtoU5IDktH/fjeSA5LJ2fyT/gsWAHEQrFkPeSzzntnRxeyUXprNFbp+EEj7f4D52hg0xzK3A+sEGSyzt+Tp35IJuuz4GGhplfXNlptPovrBiuPWCiWnGg83IfG58nX35k7tLh57iQNJ/qXHlPJmB0/xoapXS/YHY8ai5yRjvpGeTOOnrlElq2WrDafxedgK8mZC9pCsaMLDVQ6WtBcdfq+BZq30jGD5rCr5BO8kcZ5cZYKqR33HjlvlDeDztVoGmHcHb6qfBasGNTt6AhPXVw9mRot6Niwf+ENslQaRTQs6QTzqRMX+Y+tQWrYF2eF8zqvJDRDdBKjED4LuoVZKDZxaGJCp/80zaEJ+A3bDt+++wSdV9JxglkqDCTOjsfPqpZKh3/qmS2aLPUZcm376AufBV0hF4qdO7Q4KhcraXZz9Mwl8tNNDxwnb6U5uPj2lbkfGE2oN8quSM1KJhu4OFEg1HOcF3nhs6ATQhaKTQSa1FC2NNOhmQgFPaA/a1dfBCMHtan8rhXZK/XPdo+g1NXJx50dHj4LWkYsFEvn2ilblbwdiyat4oNmmsbaH0ODcWL/whtisim+8dJ6//RNZnP4LGiRYReKXU7E7Vg0DG7ffUJ8+4p8lvKM/0Y6GBXueuRl6gNkss5LqJGQIMn6uhN8FrQAzQTpdJvmCHTGbW5bOajrUz679v+cBoD4xi5ux1rNFB8VPLNIJ1tdnLiI3mWWVsBnQRQfXP1o7tAiuRhND30H8+XkzIXLjx27cMeeUzSoaESJ27Fan7yAkYNOtr6w/Xk62eriI1nxvQWm/8NnQUOoV9EBnOyMvMx57X95oGEj1mdRb8eiki6GExhdvv5vL92y63866hV0XOdXQYLPgias7EKxYn0WeTsW9XLcjgV80ISAOupfbT96qveuua0NjCVjnMBnwXCs1EKx6u1YZPG4HQuEIJbguueJV+h4zJzXx2AsGeMEPgtCWeaFYsX6LOJ2LOqm4nYseukVvEYBRguxOgwdkmlOQL3I3NwG5LDUM81SC/gsqGfZFooVt2Pdf+CcuB0Li2GDxlBfol4kVoehf9taJkZF3GUb8j1c+CzgWIaFYqmb7l94Q3wtbEO1GHZI3wXAh1gdhrqu+JMO2F1c5nIuGeMEPgu8dLRQLHm3uB1LrM8iFsPG7VigLYzVYai/dXFxdsm1/qEP+Cxw0PpCsZevLIn1WYzbsTqaI4NVizBZ9YMp6mbGqtutQB3Yd5etDXwWaLS4UKxYn0W9HYvO49oybgBsyFJp6mrc9r27g18RZ5aMcQKfBX1aWShWrs8ibscSv8WEb1+BZYBOmJwn8nRmZhdGwiwZ4wQ+C6IWilV/Llt8+0rcjhV+qAcgHt8SXNSfqWMbhZFQ36ZTtKF6OHx2tdNgoVj157LF7VhYDBusII8duzDl+nXFvPx+a+BXAsLhl4xxAp9dvQy1UKz6c9lYDBukA50/bXrguNNk8w4uztYuGeMEPrsaOR+wUKy4HUuuzyIXww6f9gLQNXRSxS/B1frFWZpqNFj8Ez67uuAXin2z+rls3I4FEof6JPXkO/acYkyW+jN1dbM0App51C4Z4wQ+u1qg7kgT0g3WQrFifRbjdix8+wqkjFgdhqaW/AzA/hXxSEKWjHECnx1/lvSFYpf0n8uWi2H7rnABkBQ0Y7h994kQAxVfKzRLmxKy/qEP+OyYc/TMpaldL2z9j1NPvPjL3eXPZYvbseYOLeJ2LDBy0ERBfFRgbnDh+xJCA2h2Qq/b+DwPPju2PP2zX92664W//JejG3Yeo8msuB0L374Co4J9TUCsDsOsvKV+SCt2VjYOx1MnLqpq4UvGOIHPjhXidqy/m/3fP/2nZ//snw9/+99PYjFsMIrQjNX4qPb8pYx80/n5rUDcPiD/jLw4qy7xNdSSMU7gs6MH+aa8GC9ux5Lrs3z9hy99/YfHb7zr8MPP9uzpAACjgrGuKz2mc7Lai63qt7giL87eseeUHGU0mR3Wsul4oF7cUH2W1PjrHvDZJNh438K9//2KuB3rhm2Hxe1Yp3rvintnu1soFoDl4dzF36kz05Ovv0PTSea73hLq/HLNWXqKPPEn8+WtzUba9LBLxghof3UKLH2Wxmbt1Bg+2wLiKynDtrr8uewb7jr853ceNm7H6mihWABWBHXJQZpUUt8O/FyBvFj8Pg0NDfndAHEHV4hNq0ifNZaMobPJQM+leav8sRzps4aaE/hsLIHf+8utn8veVC6GTd76hR1H1YOh+OL07btPNP4kFIDU2FT9yic5Hc0l+dmfylK1pLc8N29msnnls+pkVnwDfah85Pdthc+KS8y1Ng2fjaL2e3/qz2WLb1+JxbClKdMmeTA8395CsQCkA52TSa+k7j3sp7g0asTaMfRvY5PNy7FGZ5A0WsXyCGLRxaGWW8rL01B6C/RehM8GLkADn23OB+VPb1LjGeXqz2WLc3/f7Vii05AOtTS1N/XFkDYDYLSgk7Ydj5+lGYbx7VcaFCETSTGTpdFBHtfYZPPy2oUYlWT0NKGhqU/gtQsD8X1K8tnw2xzgsw15U/npTfV2LKp9eTtW7XGbFKgPNV4oFoCRQJylbSqX4DJGSsjEgrz4hm2HaYzEmGxe+qx40ZvKn3k2NwcjpudCKtCp4bNNEN+XFpNZ9XYsOr6FeyX1MHrihnL9gaHOXAAYIWgWQi4purp66Sx8pOTld8JIJMZk88pnyfTjhxsNdpIKv80BPqvx05d/ceNtD6774s6YuO6We+zCYSMRkXiFdcmIxCusS0YkXmHdeInEK6xrSYQM5MljZw1jgc9qfH7TD+yKQyAQiPC4/tZdhrHAZzXsKkMgEIhhwzAW+KyGrKbeb7JmIRWy3mLjiE+j10YmiaSRtZFJImn02sgkkTSyZDJJJI1MycQwFvisRnyDtdtatn54xGeSSBpZG5kkkkavjUwSSSNLJpNE0sjgs4HEN1i7rWXrh0d8JomkkbWRSSJp9NrIJJE0smQySSSNDD4bSHyDtdtatn54xGeSSBpZG5kkkkavjUwSSSNLJpNE0sjgs4HEN1i7rWXrh0d8JomkkbWRSSJp9NrIJJE0smQySSSNDD4bSHyDtdtatn54xGeSSBpZG5kkkkavjUwSSSNLJpNE0shWt88uzkxOTExMziyaG2y4Bts7NaExNWvvw7fW7HpdYf0+ex+9tWx9EbM3SxF3GjWZlLFvkI47EzaN01uvlU+fmLh5n7VDSBrPbl+jiKx/yNohJJN+HL5zbaGx1ywPSKMfp7deM1FUqFkug09DJFCyduuL5tbATEQOJddsP2xuDUlj0FGv3X7Y3hqWxqCvrtly2t4amEmVjK9RRIRkwjRKfRrKyF1z52lz6zBpCNZufdbcamViGMu4+2zhsZMz8zOTQTbL+qwa1HIeZ6lpLRnUbE1tpTBZz6urwWdSmKw/ARFsGoXP8uNHBJtG4bP8EBLBZlLGi9vXXLt9683elNg0yji8Ze2aLdvXc/mwaexbX/laYbj+BmIzeWiq8rXCcD0NFJZG0UBNbUWmUTRQiK3Y+r2qo876G6U2E9FL97GNUpcGPbs67L1YvhvPIZBJo6gQedijfhJwCDSMJTWfXRw44vz0xMT0fL9oZlocSqigKO8/rJ7g3dqHCrW/vbANJoOzGLa1ZNT4C5tG2evMQkewmVC/cc9hg9PgKkENNo2aepDBZpKVyRTjhxnSbBqLZSbF4OGHdF0a/aA0mhrcIEp7cBtcYBpk9/FpkN370sjCMmEaRURtJnyjZHwa+pSIaRouDX1WFNI0hrGk5rPlBJQ8sf9fQemc5WNxAUA+LB/wWwW0T9h0NsxnqeX8Z2Rca8mgZmt8OlZM3NYOzrabzZuKuds18iy30bxJu27ADCQuDf26ATOW2EwGhsIMaTaNgZvwQ7o2jf478TdKSCZ9EU+j1KZRRc2BkE+jipoDYUgmTKOIqM2Eb5SMTcM4t2COPUwaxrkFc+yRIoaxJOezfeccGKPimbr5lnvwWwWG63IwDVZFfPet6bsZ22/Kcx85ny3PqzzJcJkU5z5yPktzW3c+XBpqFNe/vFNsLg01iutf3ik2l4lSIcyQ5tJQaoMf0lwaShRjO/JILMa252AckgZ/7aIXlgZz7UJESCZMo4iozYRvlIxNAz7roX/iL41xcM7v8lR+qyB8Ohvgs6yn8K3VD9ZQRHBpaD7LdWIuE81nvf2YS0ML5RKYFVwaWiiXwKzgMjE/n3SfGHJpmJ9P1p8V2vp6dFshtWnwRi+iNg3G6GXUZtJju6iI2kx8/VMGk4bhs82uGxg+O/rXDSqHHFijYpL6w8pmma2CxdAPwfIAn2XaSQTTWrWNJINNQ5lQN7+ur8yp/df12TSUYI89bBpKsIefwEyYIR2YBj+kuTTU64CNK0S9DuivEC6NZfyYNKvLRATTKCL4TLK6Rsn4NNQx0ni8qGPEP17UTAxjSclnC5N1fggmihwPHUXqw/7UuE/IpQOuwfrt5B0/9a3VbyT34FEjII0+TA8OyKSPrxOzaRQjcSBgbg1Mo7hkUcFVC5vJIJghzaYxCH5Is2moF6wbV4h6wdpbIVwaSt8o8WbCpaH0jZJGmQjHH9DE4NRO1sIX3WLGi3LSE9JDDGNJyWcToKbBAqKmtcIiPo1eG5kkkkbWRiaJpNFrI5NE0siSySSRNDL4bCDxDdZua9n64RGfSSJpZG1kkkgavTYySSSNLJlMEkkjg88GEt9g7baWrR8e8ZkkkkbWRiaJpNFrI5NE0siSySSRNDL4bCDxDdZua9n64RGfSSJpZG1kkkgavTYySSSNLJlMEkkjg88GEt9g7baWrR8e8ZkkkkbWRiaJpNFrI5NE0siSySSRNDL4bCCymhAIBKJxGMYCn9W4/tZddpUhEAjEUGEYC3xW4+EDx+0qQyAQiPDYMfeMYSwj57O/Pzjz/sG3zVIAAEgW+CwAAHRLaj5b2Ojso+9v3PzutiO/p78vHSkeF/HoUrm1fEwxc/VSvjS7+crp/hPlY0OhKD9Yicy+Il+l0hkoAABAJyTos8JSS96+uq3w04LTjwqXVOezPp9VFIry6s9XrvRdlR4MdgAAgG5J0GcHlwUGk9kyyvlpiM+qFxac+5Tmi5ksAGBZSN5nzYlnKz4r/4TbAgA6J2mfLa4bmD5o+Gz/kms58x3WZ3NxkaG6aAsAAJ2Qts9qlw765f0Scd22uOQqLilcDZ/PapcjzPkyAAC0TGo+CwAA4wZ8FgAAugU+CwAA3QKfBQCAboHPAgBAt8BnAQCgW+CzAADQLfBZAADoFvgsAAB0C3wWAAC6BT4LAADd8v8Zxh5LNH8qsQAAAABJRU5ErkJggg==>
================================================
FILE: lesson_03/index.tr.md
================================================
**FFmpeg Assembly Üçüncü Ders**
Biraz daha jargon açıklayalım ve size kısa bir tarih dersi verelim.
**Talimat Setleri**
Önceki derste SSE2'den bahsettiğimizi, bunun bir SIMD talimat seti olduğunu görmüş olabilirsiniz. Yeni bir CPU nesli piyasaya çıktığında yeni talimatlar ve bazen daha büyük register boyutları ile gelebilir. x86 talimat setinin tarihçesi çok karmaşıktır, bu nedenle bu basitleştirilmiş bir tarihçedir (çok daha fazla alt kategori vardır):
* MMX - 1997'de piyasaya çıktı, Intel İşlemcilerinde ilk SIMD, 64-bit register'lar, tarihsel
* SSE (Streaming SIMD Extensions) - 1999'da piyasaya çıktı, 128-bit register'lar
* SSE2 - 2000'de piyasaya çıktı, birçok yeni talimat
* SSE3 - 2004'te piyasaya çıktı, ilk yatay talimatlar
* SSSE3 (Supplemental SSE3) - 2006'da piyasaya çıktı, yeni talimatlar ancak en önemlisi pshufb shuffle talimatı, tartışmasız video işlemede en önemli talimat
* SSE4 - 2008'de piyasaya çıktı, paketlenmiş minimum ve maksimum dahil birçok yeni talimat.
* AVX - 2011'de piyasaya çıktı, 256-bit register'lar (sadece float) ve yeni üç-operand sözdizimi
* AVX2 - 2013'te piyasaya çıktı, integer talimatları için 256-bit register'lar
* AVX512 - 2017'de piyasaya çıktı, 512-bit register'lar, yeni işlem maskesi özelliği. Yeni talimatlar kullanıldığında CPU frekans düşürme nedeniyle o zamanlar FFmpeg'de sınırlı kullanımları vardı. vpermb ile tam 512-bit shuffle (permute).
* AVX512ICL - 2019'da piyasaya çıktı, artık clock frekans düşürme yok.
* AVX10 - Yakında
Talimat setlerinin CPU'lara eklenmesinin yanı sıra kaldırılabileceğini de belirtmek gerekir. Örneğin AVX512, 12. Nesil Intel CPU'larında [tartışmalı şekilde kaldırılmıştır](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/). Bu nedenle FFmpeg çalışma zamanı CPU algılaması yapar. FFmpeg üzerinde çalıştığı CPU'nun yeteneklerini algılar.
Ödevde gördüğünüz gibi, fonksiyon pointer'ları varsayılan olarak C'dir ve belirli bir talimat seti varyantı ile değiştirilir. Bu, algılamanın bir kez yapıldığı ve bir daha asla yapılması gerekmediği anlamına gelir. Bu, belirli bir talimat setini hardcode eden ve mükemmel çalışan bir bilgisayarı eskiten birçok tescilli uygulamanın aksine. Bu ayrıca optimize edilmiş fonksiyonların çalışma zamanında açılıp/kapatılmasına olanak tanır. Bu, açık kaynak kodun büyük faydalarından biridir.
FFmpeg gibi programlar dünya çapında milyarlarca cihazda kullanılır, bunların bazıları çok eski olabilir. FFmpeg teknik olarak 25 yaşındaki sadece SSE destekleyen makineleri destekler! Neyse ki x86inc.asm belirli bir talimat setinde mevcut olmayan bir talimat kullanıp kullanmadığınızı söyleyebilir.
Gerçek dünya yetenekleri hakkında bir fikir vermek için, Kasım 2024 itibariyle [Steam Anketi](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam)'nden talimat seti kullanılabilirliği (bu açıkça oyunculara yönelik önyargılıdır):
| Talimat Seti | Kullanılabilirlik |
| :---- | :---- |
| SSE2 | %100 |
| SSE3 | %100 |
| SSSE3 | %99.86 |
| SSE4.1 | %99.80 |
| AVX | %97.39 |
| AVX2 | %94.44 |
| AVX512 (Steam AVX512 ve AVX512ICL arasında ayrım yapmıyor) | %14.09 |
Milyarlarca kullanıcısı olan FFmpeg gibi bir uygulama için, %0.1 bile çok büyük bir kullanıcı sayısıdır ve bir şey bozulursa hata raporları gelir. FFmpeg, [FATE test paketimizde](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F) CPU/OS/Derleyici varyasyonlarını test etmek için kapsamlı test altyapısına sahiptir. Her commit hiçbir şeyin bozulmadığından emin olmak için yüzlerce makinede çalıştırılır.
Intel detaylı talimat seti kılavuzunu burada sağlar: [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
PDF'te arama yapmak zahmetli olabilir, bu nedenle burada resmi olmayan web tabanlı bir alternatif var: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/)
SIMD talimatlarının görsel temsili de burada mevcuttur:
[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/)
x86 assembly'nin zorluklarından biri ihtiyaçlarınız için doğru talimatı bulmaktır. Bazı durumlarda talimatlar orijinal amaçlarından farklı şekilde kullanılabilir.
**Pointer offset numarası**
1. Dersten orijinal fonksiyonumuza geri dönelim, ancak C fonksiyonuna bir width argümanı ekleyelim.
Width değişkeni için int yerine ptrdiff_t kullanıyoruz, 64-bit argümanın üst 32-bitinin sıfır olduğundan emin olmak için. Eğer fonksiyon imzasında doğrudan bir int width geçirsek ve sonra bunu pointer aritmetiği için quad olarak kullanmaya çalışsak (`widthq` kullanarak) register'ın üst 32-biti rastgele değerlerle doldurulabilir. Bunu `movsxd` ile width'i işaret genişleterek düzeltebilirdik (x86inc.asm'daki `movsxdifnidn` makrosuna da bakın), ancak bu daha kolay bir yol.
Aşağıdaki fonksiyon pointer offset numarasını içerir:
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width)
INIT_XMM sse2
cglobal add_values, 3, 3, 2, src, src2, width
add srcq, widthq
add src2q, widthq
neg widthq
.loop
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
paddb m0, m1
movu [srcq+widthq], m0
add widthq, mmsize
jl .loop
RET
```
Kafa karıştırabileceği için bunu adım adım gözden geçirelim:
```assembly
add srcq, widthq
add src2q, widthq
neg widthq
```
Width her pointer'a eklenir böylece her pointer artık işlenecek buffer'ın sonunu işaret eder. Width daha sonra negatif yapılır.
```assembly
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
```
Yüklemeler daha sonra widthq negatif olacak şekilde yapılır. Bu nedenle ilk iterasyonda [srcq+widthq] srcq'nun orijinal adresini işaret eder, yani buffer'ın başlangıcına geri işaret eder.
```assembly
add widthq, mmsize
jl .loop
```
mmsize negatif widthq'ya eklenir ve onu sıfıra yaklaştırır. Döngü koşulu artık jl'dir (sıfırdan küçükse atla). Bu numara widthq'nun aynı anda pointer offset **ve** döngü sayacı olarak kullanılması anlamına gelir, cmp talimatından tasarruf sağlar. Ayrıca pointer offset'inin birden fazla yüklemede ve saklamada kullanılmasına ve gerektiğinde pointer offset'lerinin katlarının kullanılmasına olanak tanır (ödev için bunu hatırlayın).
**Hizalama**
Tüm örneklerde hizalama konusunu önlemek için movu kullanıyorduk. Birçok CPU, veri hizalanmışsa, yani bellek adresi SIMD register boyutuna bölünebiliyorsa veriyi daha hızlı yükleyip saklayabilir. Mümkün olduğunda FFmpeg'de mova kullanarak hizalanmış yükleme ve saklamaları kullanmaya çalışırız.
FFmpeg'de av_malloc heap'te hizalanmış bellek sağlayabilir ve DECLARE_ALIGNED C ön işlemci direktifi stack'te hizalanmış bellek sağlayabilir. mova hizalanmamış bir adresle kullanılırsa, segmentasyon hatası oluşturacak ve uygulama çökecektir. Ayrıca hizalama değerinin SIMD register boyutuna karşılık geldiğinden emin olmak önemlidir, yani xmm ile 16, ymm için 32 ve zmm için 64.
RODATA bölümünün başlangıcını 64-byte'a hizalamak için:
```assembly
SECTION_RODATA 64
```
Bunun sadece RODATA'nın başlangıcını hizaladığını unutmayın. Bir sonraki etiketin 64-byte sınırında kalmasını sağlamak için dolgu byte'ları gerekebilir.
**Aralık genişletmesi**
Şimdiye kadar kaçındığımız bir diğer konu da taşmadır. Bu, örneğin toplama veya çarpma gibi bir işlemden sonra bir byte'ın değeri 255'i geçtiğinde olur. Ara değerin bir byte'dan (örn. word'ler) daha büyük olmasına ihtiyaç duyabiliriz veya potansiyel olarak veriyi o daha büyük ara boyutta bırakmak isteyebiliriz.
İşaretsiz byte'lar için punpcklbw (packed unpack low bytes to words - paketlenmiş düşük byte'ları word'lere açma) ve punpckhbw (packed unpack high bytes to words - paketlenmiş yüksek byte'ları word'lere açma) devreye girer.
punpcklbw'nin nasıl çalıştığına bakalım. Intel Kılavuzundan SSE2 sürümü için sözdizimi şöyledir:
| PUNPCKLBW xmm1, xmm2/m128 |
| :---- |
Bu, kaynağının (sağ taraf) bir xmm register'ı veya bellek adresi (m128, standart [base + scale*index + disp] sözdizimi ile bir bellek adresi anlamına gelir) ve hedefin bir xmm register'ı olabileceği anlamına gelir.
Yukarıdaki officedaytime.com web sitesi neler olup bittiğini gösteren iyi bir diyagrama sahiptir:

Byte'ların her register'ın alt yarısından sırasıyla interleave edildiğini görebilirsiniz. Peki bunun aralık genişletmesi ile ne ilgisi var? Eğer src register'ı tamamen sıfırsa, bu dst'deki byte'ları sıfırlarla interleave eder. Bu, byte'lar işaretsiz olduğu için *sıfır genişletmesi* olarak bilinir. punpckhbw yüksek byte'lar için aynı şeyi yapmak için kullanılabilir.
Bunun nasıl yapıldığını gösteren bir parçacık:
```assembly
pxor m2, m2 ; m2'yi sıfırla
movu m0, [srcq]
movu m1, m0 ; m0'ın bir kopyasını m1'de yap
punpcklbw m0, m2
punpckhbw m1, m2
```
```m0``` ve ```m1``` artık orijinal byte'ları word'lere sıfır genişletilmiş olarak içerir. Bir sonraki derste AVX'deki üç-operand talimatlarının ikinci movu'yu nasıl gereksiz kıldığını göreceksiniz.
**İşaret genişletmesi**
İşaretli veri biraz daha karmaşık. İşaretli bir integer'ı aralık genişletmek için [işaret genişletmesi](https://en.wikipedia.org/wiki/Sign_extension) olarak bilinen bir işlem kullanmamız gerekir. Bu MSB'leri işaret biti ile doldurur. Örneğin: int8_t'de -2, 0b11111110'dur. int16_t'ye işaret genişletmek için 1'in MSB'si 0b1111111111111110 yapmak için tekrarlanır.
```pcmpgtb``` (packed compare greater than byte - paketlenmiş byte'dan büyük karşılaştırma) işaret genişletmesi için kullanılabilir. (0 > byte) karşılaştırmasını yaparak, eğer byte negatifse hedef byte'daki tüm bitler 1'e ayarlanır, aksi takdirde hedef byte'daki bitler 0'a ayarlanır. punpckX yukarıdaki gibi işaret genişletmesi gerçekleştirmek için kullanılabilir. Eğer byte negatifse karşılık gelen byte 0b11111111'dir ve aksi takdirde 0x00000000'dır. Byte değerini pcmpgtb'nin çıktısı ile interleave etmek sonuç olarak word'e işaret genişletmesi gerçekleştirir.
```assembly
pxor m2, m2 ; m2'yi sıfırla
movu m0, [srcq]
movu m1, m0 ; m0'ın bir kopyasını m1'de yap
pcmpgtb m2, m0
punpcklbw m0, m2
punpckhbw m1, m2
```
Gördüğünüz gibi işaretsiz duruma kıyasla bir ekstra talimat var.
**Paketleme**
packuswb (pack unsigned word to byte - işaretsiz word'ü byte'a paketleme) ve packsswb word'den byte'a gitmenizi sağlar. Word içeren iki SIMD register'ını byte içeren bir SIMD register'ında interleave etmenizi sağlar. Değerler byte aralığını aşarsa, doyurulacaklarını (yani en büyük değerde kısıtlanacaklarını) unutmayın.
**Shuffle'lar**
Shuffle'lar, permute olarak da bilinirler, tartışmasız video işlemede en önemli talimattır ve SSSE3'te mevcut olan pshufb (packed shuffle bytes - paketlenmiş byte shuffle), en önemli varyantıdır.
Her byte için karşılık gelen kaynak byte hedef register'ının bir indeksi olarak kullanılır, MSB ayarlandığında hariç hedef byte sıfırlanır. Aşağıdaki C koduna benzerdir (SIMD'de tüm 16 döngü iterasyonu paralel olarak gerçekleşir):
```c
uint8_t tmp[16];
memcpy(tmp, dst, 16);
for(int i = 0; i < 16; i++) {
if(src[i] & 0x80)
dst[i] = 0;
else
dst[i] = tmp[src[i]];
}
```
İşte basit bir assembly örneği:
```assembly
SECTION_DATA 64
shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1
section .text
movu m0, [srcq]
movu m1, [shuffle_mask]
pshufb m0, m1 ; m1'e göre m0'ı shuffle et
```
Kolay okunabilirlik için çıktı byte'ını sıfırlamak için shuffle indeksi olarak -1 kullanıldığını unutmayın: byte olarak -1, 0b11111111 bit alanıdır (iki'nin tümleyeni), ve bu nedenle MSB (0x80) ayarlanmıştır.
================================================
FILE: lesson_03/index.zh.md
================================================
**FFmpeg 汇编语言第三课**
下面解释更多术语,并简要回顾一下历史。
**指令集(Instruction Sets)**
你可能在上一课中已经看到讨论了 SSE2,这是一组 SIMD 指令集。当新一代 CPU 发布时,可能会带来新的指令,有时还会有更大的寄存器。x86 指令集的历史非常复杂,以下是简化版本(实际还有更多子类别):
* MMX - 1997 年推出,Intel 处理器中的第一个 SIMD,64 位寄存器,具有历史意义
* SSE (Streaming SIMD Extensions) - 1999 年推出,128 位寄存器
* SSE2 - 2000 年推出,许多新指令
* SSE3 - 2004 年推出,第一批水平指令
* SSSE3 (Supplemental SSE3) - 2006 年推出,新指令,但最重要的是 pshufb 洗牌指令,可以说是视频处理中最重要的指令
* SSE4 - 2008 年推出,许多新指令,包括打包的最小值和最大值。
* AVX - 2011 年推出,256 位寄存器(仅浮点)和新的三操作数语法
* AVX2 - 2013 年推出,用于整数指令的 256 位寄存器
* AVX512 - 2017 年推出,512 位寄存器,新的操作掩码功能。由于使用新指令时 CPU 会降频,当时在 FFmpeg 中的使用有限。具有 vpermb 的全 512 位洗牌(排列)。
* AVX512ICL - 2019 年推出,不再有主频降低。
* AVX10 - 即将推出
值得注意的是,指令集既可以被添加到 CPU 中,也可以被移除。例如,AVX512 在第 12 代 Intel CPU 中被有争议地[移除](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/)了。正因如此,FFmpeg 会进行运行时 CPU 检测,检测当前 CPU 支持的功能。
正如你在作业中看到的,函数指针默认指向 C 实现,并在运行时被特定指令集的变体替换。这意味着检测只做一次,之后无需重复。这与许多专有应用形成鲜明对比——后者硬编码特定指令集,导致功能完好的计算机被淘汰。运行时检测还允许动态开启/关闭优化函数,这是开源的一大优势。
像 FFmpeg 这样的程序在全球数十亿台设备上运行,其中一些可能非常古老。FFmpeg 技术上甚至支持仅有 SSE 的机器——那些已有 25 年历史的机器!好在 x86inc.asm 能够在你使用了某个指令集中不存在的指令时发出警告。
为了让你了解实际情况,以下是截至 2024 年 11 月来自 [Steam 调查](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) 的指令集可用性(这显然偏向于游戏玩家):
| 指令集 | 可用性 |
| :---- | :---- |
| SSE2 | 100% |
| SSE3 | 100% |
| SSSE3 | 99.86% |
| SSE4.1 | 99.80% |
| AVX | 97.39% |
| AVX2 | 94.44% |
| AVX512 (Steam 不区分 AVX512 和 AVX512ICL) | 14.09% |
对于像 FFmpeg 这样拥有数十亿用户的应用来说,即使 0.1% 也意味着大量用户,一旦出问题就会产生大量错误报告。FFmpeg 拥有完善的测试基础设施,通过 [FATE 测试套件](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F) 测试 CPU/操作系统/编译器的各种组合。每次提交都会在数百台机器上运行,以确保没有任何东西被破坏。
Intel 在这里提供了详细的指令集手册:[https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
搜索 PDF 可能比较麻烦,这里有一个非官方的网页版替代方案:[https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/)
这里还有一个 SIMD 指令的可视化展示:
[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/)
x86 汇编的一大挑战在于找到适合需求的指令。有时候指令可以用于其最初设计意图之外的场景。
**指针偏移技巧(Pointer offset trickery)**
回到第一课中的原始函数,但在 C 函数中添加一个 width 参数。
这里使用 ptrdiff_t 而不是 int 作为 width 变量,以确保 64 位参数的高 32 位为零。如果直接在函数签名中传递 int 类型的 width,然后将其作为四字用于指针算术(即使用 `widthq`),寄存器的高 32 位可能包含任意值。虽然可以用 `movsxd` 进行符号扩展来解决(也可参见 x86inc.asm 中的宏 `movsxdifnidn`),但使用 ptrdiff_t 是更简单的做法。
下面的函数包含指针偏移技巧:
```assembly
;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width)
INIT_XMM sse2
cglobal add_values, 3, 3, 2, src, src2, width
add srcq, widthq
add src2q, widthq
neg widthq
.loop
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
paddb m0, m1
movu [srcq+widthq], m0
add widthq, mmsize
jl .loop
RET
```
逐步分析如下:
```assembly
add srcq, widthq
add src2q, widthq
neg widthq
```
width 被加到每个指针上,使它们指向缓冲区的末尾,然后 width 被取反。
```assembly
movu m0, [srcq+widthq]
movu m1, [src2q+widthq]
```
加载时 widthq 为负值。因此第一次迭代时 [srcq+widthq] 指向 srcq 的原始地址,即缓冲区的开头。
```assembly
add widthq, mmsize
jl .loop
```
mmsize 被加到负的 widthq 上,使其逐渐趋近于零。循环条件是 jl(小于零则跳转)。这个技巧让 widthq 同时充当指针偏移量**和**循环计数器,节省了一条 cmp 指令。它还允许在多次加载和存储中复用该偏移量,并在需要时使用偏移量的倍数(作业中会用到这一点)。
**对齐(Alignment)**
在之前的所有例子中,一直使用 movu 来回避对齐话题。许多 CPU 在数据对齐时(即内存地址能被 SIMD 寄存器大小整除时)可以更快地加载和存储数据。在 FFmpeg 中,尽可能使用 mova 进行对齐的加载和存储。
在 FFmpeg 中,av_malloc 能够在堆上分配对齐的内存,DECLARE_ALIGNED C 预处理器指令可以在栈上声明对齐的内存。如果对未对齐的地址使用 mova,会导致段错误(segmentation fault)并使程序崩溃。确保对齐值与 SIMD 寄存器大小匹配也很重要:xmm 为 16,ymm 为 32,zmm 为 64。
以下是如何将 RODATA 段的开头对齐到 64 字节:
```assembly
SECTION_RODATA 64
```
注意这只是对齐 RODATA 的开头。可能需要填充字节以确保下一个标签保持在 64 字节边界上。
**范围扩展(Range expansion)**
之前回避的另一个话题是溢出。例如,当一个字节的值在加法或乘法后超过 255 时就会发生溢出。有时需要比字节更大的中间值(如字)来完成运算,或者需要将数据保留在更大的中间尺寸中。
对于无符号字节,这就是 punpcklbw(packed unpack low bytes to words,即打包解包低位字节到字)和 punpckhbw(packed unpack high bytes to words,即打包解包高位字节到字)派上用场的地方。
来看 punpcklbw 是如何工作的。Intel 手册中的 SSE2 版本的语法如下:
| PUNPCKLBW xmm1, xmm2/m128 |
| :---- |
这意味着它的源操作数(右侧)可以是一个 xmm 寄存器或内存地址(m128 表示具有标准 [base + scale*index + disp] 语法的内存地址),目标操作数是一个 xmm 寄存器。
上面的 officedaytime.com 网站有一个很好的图表展示了发生了什么:

你可以看到字节分别从两个寄存器的低半部分交错排列。但这与范围扩展有什么关系?如果 src 寄存器全为零,就会把 dst 中的字节与零交错——这就是所谓的*零扩展*(zero extension),因为字节是无符号的。punpckhbw 可用于对高位字节做同样的操作。
这是一个展示如何做到这一点的片段:
```assembly
pxor m2, m2 ; 将 m2 清零
movu m0, [srcq]
movu m1, m0 ; 将 m0 复制到 m1
punpcklbw m0, m2
punpckhbw m1, m2
```
```m0``` 和 ```m1``` 现在包含零扩展为字的原始字节。在下一课中,你将看到 AVX 中的三操作数指令如何使第二个 movu 变得不必要。
**符号扩展(Sign extension)**
有符号数据稍微复杂一些。要对有符号整数进行范围扩展,需要使用[符号扩展](https://en.wikipedia.org/wiki/Sign_extension)(sign extension)。它用符号位填充高位(MSB)。例如:int8_t 中的 -2 是 0b11111110。将其符号扩展为 int16_t 时,MSB 的 1 被重复,得到 0b1111111111111110。
```pcmpgtb```(packed compare greater than byte,即打包比较大于字节)可用于符号扩展。通过比较(0 > 字节),如果字节为负,目标字节的所有位被设为 1;否则设为 0。然后用 punpckX 执行符号扩展:负字节对应 0b11111111,非负字节对应 0x00000000。将原始字节与 pcmpgtb 的结果交错,即完成了到字的符号扩展。
```assembly
pxor m2, m2 ; 将 m2 清零
movu m0, [srcq]
movu m1, m0 ; 将 m0 复制到 m1
pcmpgtb m2, m0
punpcklbw m0, m2
punpckhbw m1, m2
```
正如你所看到的,与无符号情况相比,多了一条指令。
**打包(Packing)**
packuswb(pack unsigned word to byte,即打包无符号字到字节)和 packsswb 可以将字转换为字节。它们将两个包含字的 SIMD 寄存器合并为一个包含字节的 SIMD 寄存器。注意,如果值超出字节范围,会被饱和(saturated,即截断到最大值)。
**洗牌(Shuffles)**
洗牌(Shuffles),也称为排列(permutes),可以说是视频处理中最重要的指令。SSSE3 中的 pshufb(packed shuffle bytes,即打包洗牌字节)是其中最重要的变体。
对于每个字节,源字节的值被用作目标寄存器的索引;如果 MSB 被设置,则目标字节被清零。这类似于以下 C 代码(不过在 SIMD 中 16 次迭代是并行发生的):
```c
for(int i = 0; i < 16; i++) {
if(src[i] & 0x80)
dst[i] = 0;
else
dst[i] = dst[src[i]]
}
```
这是一个简单的汇编示例:
```assembly
SECTION_DATA 64
shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1
section .text
movu m0, [srcq]
movu m1, [shuffle_mask]
pshufb m0, m1 ; 基于 m1 洗牌 m0
```
注意这里用 -1 作为洗牌索引来清零输出字节,以便于阅读:-1 作为字节是 0b11111111(二进制补码),因此 MSB(0x80)被设置。
gitextract_u2mmhjd8/
├── README.es.md
├── README.fr.md
├── README.md
├── README.tr.md
├── README.zh.md
├── lesson_01/
│ ├── index.es.md
│ ├── index.fr.md
│ ├── index.md
│ ├── index.tr.md
│ └── index.zh.md
├── lesson_02/
│ ├── index.es.md
│ ├── index.fr.md
│ ├── index.md
│ ├── index.tr.md
│ └── index.zh.md
└── lesson_03/
├── index.es.md
├── index.fr.md
├── index.md
├── index.tr.md
└── index.zh.md
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (193K chars).
[
{
"path": "README.es.md",
"chars": 1011,
"preview": "Bienvenido a la Escuela de Lenguaje Ensamblador de FFmpeg. Has dado el primer paso en el viaje más interesante, desafian"
},
{
"path": "README.fr.md",
"chars": 1164,
"preview": "Bienvenue dans la FFmpeg School of Assembly Language. Vous avez fait le premier pas dans le voyage le plus intéressant, "
},
{
"path": "README.md",
"chars": 1015,
"preview": "Welcome to the FFmpeg School of Assembly Language. You have taken the first step on the most interesting, challenging, a"
},
{
"path": "README.tr.md",
"chars": 941,
"preview": "FFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, zorlu ve ödüllendirici yolculuğunda ilk adımı attın"
},
{
"path": "README.zh.md",
"chars": 501,
"preview": "欢迎来到 FFmpeg 汇编语言学校。你已经迈出了编程中最有趣、最具挑战、最具回报的一段旅程的第一步。这些课程将为你打下 FFmpeg 中汇编语言编写的基础,并让你大开眼界,了解计算机内部的实际运作方式。\n\n**必备知识**\n\n* C 语言"
},
{
"path": "lesson_01/index.es.md",
"chars": 15993,
"preview": "**Lección Uno de Lenguaje Ensamblador FFmpeg**\n\n**Introducción**\n\nBienvenido a la Escuela de Lenguaje Ensamblador de FFm"
},
{
"path": "lesson_01/index.fr.md",
"chars": 16843,
"preview": "**FFmpeg Assembly Language Leçon Une**\n\n**Introduction**\n\nBienvenue dans la FFmpeg School of Assembly Language. Vous ave"
},
{
"path": "lesson_01/index.md",
"chars": 14232,
"preview": "**FFmpeg Assembly Language Lesson One**\n\n**Introduction**\n\nWelcome to the FFmpeg School of Assembly Language. You have t"
},
{
"path": "lesson_01/index.tr.md",
"chars": 14398,
"preview": "**FFmpeg Assembly Dili Birinci Ders**\n\n**Giriş**\n\nFFmpeg Assembly Dili Okuluna hoş geldiniz. Programlamanın en ilginç, z"
},
{
"path": "lesson_01/index.zh.md",
"chars": 6888,
"preview": "**FFmpeg 汇编语言第一课**\n\n**简介**\n\n欢迎来到 FFmpeg 汇编语言学校。你已经迈出了编程中最有趣、最具挑战、最具回报的一段旅程的第一步。这些课程将为你打下 FFmpeg 中汇编语言编写的基础,并让你大开眼界,了解计算机"
},
{
"path": "lesson_02/index.es.md",
"chars": 8233,
"preview": "**Lección Dos de Lenguaje Ensamblador con FFmpeg**\n\nAhora que has escrito tu primera función en lenguaje ensamblador, va"
},
{
"path": "lesson_02/index.fr.md",
"chars": 9472,
"preview": "**FFmpeg Assembly Language Leçon Deux**\n\nMaintenant que vous avez écrit votre première fonction en assembleur, nous allo"
},
{
"path": "lesson_02/index.md",
"chars": 7390,
"preview": "**FFmpeg Assembly Language Lesson Two**\n\nNow that you’ve written your first assembly language function, we will now intr"
},
{
"path": "lesson_02/index.tr.md",
"chars": 7462,
"preview": "**FFmpeg Assembly Dili İkinci Ders**\n\nArtık ilk assembly dili fonksiyonunuzu yazdığınıza göre, şimdi dallanma ve döngüle"
},
{
"path": "lesson_02/index.zh.md",
"chars": 3899,
"preview": "**FFmpeg 汇编语言第二课**\n\n既然你已经写了你的第一个汇编语言函数,现在将介绍分支和循环。\n\n首先介绍标签(label)和跳转的概念。在下面的人工示例中,jmp 指令将代码执行移动到 \".loop:\" 之后。\".loop:\" 被称"
},
{
"path": "lesson_03/index.es.md",
"chars": 24381,
"preview": "**Lección Tres de Ensamblador con FFmpeg**\n\nVamos a explicar algo más de jerga y darte una breve lección de historia.\n\n*"
},
{
"path": "lesson_03/index.fr.md",
"chars": 13502,
"preview": "**FFmpeg Assembly Language Leçon Trois**\n\nExpliquons un peu plus de jargon et lisez bien cette petite leçon d'histoire.\n"
},
{
"path": "lesson_03/index.md",
"chars": 23689,
"preview": "**FFmpeg Assembly Lesson Three**\n\nLet’s explain some more jargon and give you a short history lesson.\n\n**Instruction Set"
},
{
"path": "lesson_03/index.tr.md",
"chars": 11695,
"preview": "**FFmpeg Assembly Üçüncü Ders**\n\nBiraz daha jargon açıklayalım ve size kısa bir tarih dersi verelim.\n\n**Talimat Setleri*"
},
{
"path": "lesson_03/index.zh.md",
"chars": 5935,
"preview": "**FFmpeg 汇编语言第三课**\n\n下面解释更多术语,并简要回顾一下历史。\n\n**指令集(Instruction Sets)**\n\n你可能在上一课中已经看到讨论了 SSE2,这是一组 SIMD 指令集。当新一代 CPU 发布时,可能会带"
}
]
About this extraction
This page contains the full source code of the FFmpeg/asm-lessons GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (184.2 KB), approximately 66.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.