From 3128554e3b503a4bcf7210d37f00a94dbc50bc2f Mon Sep 17 00:00:00 2001 From: LXIII Date: Fri, 8 Aug 2025 17:42:14 +0200 Subject: [PATCH] correction FR Correction of typos, along with some simplifications and additions. --- README.fr.md | 8 ++--- lesson_01/index.fr.md | 75 ++++++++++++++++++++++--------------------- lesson_02/index.fr.md | 56 ++++++++++++++++---------------- lesson_03/index.fr.md | 64 ++++++++++++++++++------------------ 4 files changed, 102 insertions(+), 101 deletions(-) diff --git a/README.fr.md b/README.fr.md index b9b1fa8..df002cb 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,15 +1,15 @@ -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... +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 à 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...) +* 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. +Dans ce répo Git se trouvent les leçons et des exercices (non inclus actuellement) relatifs à chaque leçon. À 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 : +Un serveur Discord est accessible pour répondre à vos questions : https://discord.com/invite/Ks5MhUhqfB **Traductions** diff --git a/lesson_01/index.fr.md b/lesson_01/index.fr.md index 37d2f71..52b4fd1 100644 --- a/lesson_01/index.fr.md +++ b/lesson_01/index.fr.md @@ -2,85 +2,86 @@ **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 sr la façon sur la manière l'assembleur est utilisée dns FFmpeg et vous ouvriront les yeux sur ce qui se passe réellement dans votre ordinateur... +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 à 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...) +* Mathématiques de lycée (scalaire vs vecteur, addition, multiplication, etc.) -**Qu'est ce que l'assembleur?** +**Qu'est-ce que l'assembleur ?** -L'assembleur est un langage de programmation où le code que vous écrivez correponds direction à des instructions compréhensible par un CPU. L'assembleur lisible par l'Homme est, comme son nom l'indique, *assemblé* en données binaires, connu sous le nom de *langage machine*, que le CPU peut comprendre. Le code assembleur est souvent appelé "assembleur" ou "asm" en ambrégé. +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'Homme est, comme son nom l'indique, *assemblé* en données binaires, connu sous le nom de *langage machine*, que le CPU peut comprendre. Le code assembleur est souvent appelé "assembleur" ou "asm" en abrégé. -La grand 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. +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, des vidéos et de l'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. +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`. +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ééns ont écrit de l'assembleur dans FFmpeg. Comme partout, l'apprentissage c'est 50% du jargon et 50% d'apprentissage réel. +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% de jargon et 50% d'apprentissage réel. -**Pourquoi écrivons en assembleur ?** +**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és au monde, aussi bien par les utilisateurs finaux que par les multi-nationales dans leurs data-centers. Donc, même une petit amélioration apporte beaucoup. +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 saccades. 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 multi-nationales 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 ressemblants à du C mais qui sont en fait des instructions en assembleur utilisées pour pemettre 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 lente 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 compliquent leur lecture. +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 compliquent leur lecture. -Aussi, vous verrez des référence à 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'utilisations 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. +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 beaucou d'experts auto-proclamés sur Internet disant que rien de tout cela 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és un gain de vitesse d'environ x2 grâce à cette vectorisation automatique, tandis que pour des versions écrites à la main, ce gain montait à x8. +Enfin, vous verrez beaucoup d'experts auto-proclamé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 continue de fonctionner 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 syntaxes pour l'assembleur x86 que vous trouverez en ligne : AT&T et Intel. La première est plus ancienne et plus difficile à lire comparé à la seconde. Nous nous intéresserons à cette dernière. +Il existe deux types de syntaxes 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é autout 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. +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 beaucoup 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. +Beaucoup de livres détaillent de nombreuses architectures d'ordinateurs différentes 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/) +Cela 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: +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és 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 endroit sans d'abord passer ces données dans un registre. +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 endroit 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érale (GPR). Les GPR sont appelés ainsi car ils peuvent contenir soit des données, 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... +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, 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 concsacrés aux subtilités des GPR, leur histoire, etc... Car les GPR ont joués 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 compléxités ne sont pas nécessaires et sont abstraites. +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, leurs complexités ne sont pas nécessaires et sont abstraites. **Registres vectoriels** -Les registrs 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, historique et peu utilisés de nos jours -* registres `xmm` : des registres `XMM`, de taille 128 bits, largement disponible -* registres `ymm` : des registres `YMM`, de tailles 256 bits, avec quelques complications lors de leur utilisation -* registres `zmm` : des registres `ZMM`, de tailles 512 bits, très peu disponible +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 : -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: +* 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): +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): +Ou quatre double mots (entiers de 32 bits) : | a | b | c | d | | :---- | :---- | :---- | :---- | -Ou deux quadruples mots (entiers de 64 bits): +Ou deux quadruples mots (entiers de 64 bits) : | a | b | | :---- | :---- | @@ -93,11 +94,11 @@ Pour récapituler : * **q**uadwords - données de 64 bits * **d**ouble **q**uadwords - données de 128 bits. -Les caractères en gras seront important dans la suite. +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 autre d'étiquetter 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. +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** @@ -142,7 +143,7 @@ Passons en revue le code ligne par ligne : %include "x86inc.asm" ``` -Ce `header` développé dans les communautés x264, FFmpeg et dav1d pour fournir des axiliaires, des noms prédéfinis et des macros (comme `cglobal` ci-dessous) pour simpliflier l'écriture du code assembleur. +Ce `header` développé dans les communautés x264, FFmpeg et dav1d pour fournir des auxiliaires, des noms prédéfinis et des macros (comme `cglobal` ci-dessous) pour simplifier l'écriture du code assembleur. ```assembly SECTION .text @@ -154,7 +155,7 @@ Cela désigne la section où le code que vous voulez exécuter est placé. Cela INIT_XMM sse2 ``` -La première ligne est un commentaire (le point-virgule `;` en asm 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 un registre XMM, en utilisant le jeu d'instruction 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. +La première ligne est un commentaire (le point-virgule `;` en asm 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 un registre XMM, en utilisant 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 @@ -164,19 +165,19 @@ Le code précédent montre une ligne importante : la définition d'une fonction Passons en revue chaque élément de la ligne un par un : -* Le paramètre suivant indique que la fonction prend deux argument (le premier 2). +* Le paramètre suivant indique que la fonction prend deux arguments (le premier 2). * Ensuite, le deuxième 2 indique que nous allons utiliser deux GPR pour les arguments. Dans certains cas, nous pourrions vouloir utiliser plus de GPR, donc nous devrons indiquer à x86util que nous en aurons besoin de plus. * Le paramètre suivant indique à x86util combien de registres `XMM` nous allons utiliser. * 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 les labels comme arguments de fonctions mais plutôt les adresses des registres GPR à la place, en utilisant `r0`, `r1`, etc... +Il est important de noter que le code plus ancien peut ne pas avoir les 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). +`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. diff --git a/lesson_02/index.fr.md b/lesson_02/index.fr.md index ae23879..80bb61b 100644 --- a/lesson_02/index.fr.md +++ b/lesson_02/index.fr.md @@ -15,9 +15,9 @@ L'instruction `jmp` déplace l'exécution du code après `.loop:`. `.loop:` est 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'opérations basé 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*). +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 mis à jour en fonction du résultat de la plupart 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. +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 la boucle se répète jusqu'à ce que `r0q` atteigne zéro, moment où la boucle s'arrête. ```assembly mov r0q, 3 @@ -47,7 +47,7 @@ for(i = 0; i < 3; i++) { } ``` -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```): +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 @@ -58,25 +58,25 @@ xor r0q, r0q 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ème que ```mov r0q, 0```, parce qu'il n'y a aucun chargement de registres. -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```. +Plusieurs choses sont à examiner dans 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`, car aucune valeur immédiate n'est chargée. +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. +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 Egal / Zéro | ZF = 1 | -| JNE/JNZ | Jump if Not Equal/Not Zero | Saut si Non Egalité / Non Zéro | ZF = 0 | -| JG/JNLE | Jump if Greater/Not Less or Equal (signed) | Saut si Supériorité / Non Infériorité ou Egalité (signé) | ZF = 0 and SF = OF | -| JGE/JNL | Jump if Greater or Equal/Not Less (signed) | Saut si Supériorité ou Egalité / Non Infériorité (signé) | SF = OF | -| JL/JNGE | Jump if Less/Not Greater or Equal (signed) | Saut si Infériorité / Non Supériorité ou Egalité (signé) | SF ≠ OF | -| JLE/JNG | Jump if Less or Equal/Not Greater (signed) | Saut si Infériorité ou Egalité / Non Supériorité (signé) | ZF = 1 or SF ≠ OF | +| JE/JZ | **J**ump if **E**qual/**Z**ero | Saut si Egal / Zéro | ZF = 1 | +| JNE/JNZ | **J**ump if **N**ot **E**qual/**N**ot **Z**ero | Saut si Non Égal / Non Zéro | ZF = 0 | +| JG/JNLE | **J**ump if **G**reater/**N**ot **L**ess or **E**qual (signed) | Saut si Supérieur / Non Inférieur ou Égal (signé) | ZF = 0 et SF = OF | +| JGE/JNL | **J**ump if **G**reater or **E**qual/**N**ot **L**ess (signed) | Saut si Supérieur ou Égal / Non Inférieur (signé) | SF = OF | +| JL/JNGE | **J**ump if **L**ess/**N**ot **G**reater or **E**qual (signed) | Saut si Inférieur / Non Supérieur ou Égal (signé) | SF ≠ OF | +| JLE/JNG | **J**ump if **L**ess or **E**qual/**N**ot **G**reater (signed) | Saut si Inférieur ou Égal / Non Supérieur (signé) | ZF = 1 ou SF ≠ OF | **Constantes** -Plongeons dans quelques exemples sur l'utilisation des constantes: +Plongeons dans quelques exemples sur l'utilisation des constantes : ```assembly SECTION_RODATA @@ -86,16 +86,16 @@ 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éclare 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}; +* 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 addresse mémoire, peuvent être utilisés pour les chargements (mais pas pour des stockages car ils sont en lecture seule). Quelques instructions acceptent une addresse 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). +Ces labels, que l'assembleur convertit en adresses 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ée. +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: +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]; @@ -107,7 +107,7 @@ for(i = 0; i < 3; 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 addresses mémoires. Cela s'applique à tout type d'addresses mémoires: +Regardons la syntaxe du calcul d'adresses mémoire. Cela s'applique à tout type d'adresses mémoires : ```assembly [base + scale*index + disp] @@ -116,11 +116,11 @@ Regardons la syntaxe du calcul des addresses mémoires. Cela s'applique à tout * 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 le données. +* 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 leque vous travaillez. +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: +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) @@ -140,23 +140,23 @@ 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 remplacements par un unique ```add```. +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 maitrisez 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. +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: +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 par 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 régistres temporaires (ce code n'a pas d'équivalent car ```add``` modifie les *FLAGS*): +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 régistres temporaires (ce code n'a pas d'équivalent car `add` modifie les *FLAGS*) : ```assembly movq r0q, r1q @@ -166,8 +166,8 @@ add r3q, 5 add r0q, r3q ``` -Vous verrez l'utilisation de ```lea``` dans l'utilisation 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. +Vous verrez `lea` utilisée pour manipuler de nombreuses 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 additioner les valeurs à un vecteur **SIMD** dans une boucle. +Dans l'exercice, vous aurez à charger une constante et d'en additionner les valeurs à un vecteur **SIMD** dans une boucle. [Leçon suivante](../lesson_03/index.fr.md) diff --git a/lesson_03/index.fr.md b/lesson_03/index.fr.md index c7fe5a1..fb9bb47 100644 --- a/lesson_03/index.fr.md +++ b/lesson_03/index.fr.md @@ -1,33 +1,33 @@ **FFmpeg Assembly Language Leçon Trois** -Expliquons un peu plus de jargon et lisez bien cette petite leçon d'histoire. +Expliquons un peu plus de jargon et suivez bien cette petite leçon d'histoire. **Jeux d'instructions** -Vous avez surêment prêté attention au fait que dans la leçon précédente, noud avons parlé de **SSE2** qui fait parti 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 quelques fois des 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): +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 quelques fois des registres de tailles plus grandes. L'histoire du jeu d'instructions 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 - Launchée en 2008, ajout de nombreuses nouvelles instructions, notamment les minimums et maximums en mode vectorisé. -* AVX - Launchée en 2011, ajout de registres à 256 bits (uniquement pour les flottants) et d'une nouvelle syntaxe à trois opérandes. -* AVX2 - Launchée en 2013, ajout de registres à 256 bits pour les registres 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```. +* 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 sur entiers. +* AVX512 - Lancée en 2017, registres à 512 bits, nouvelle instruction de masquage. Leur utilisation dans FFmpeg était très limitée en raison d'une baisse de fréquence du CPU lorsque ces 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 - A venir. +* AVX10 - À venir, comme indiqué dans la première version de la spécification d'Intel en [juillet 2023](https://www.intel.fr/content/www/fr/fr/content-details/784267/intel-advanced-vector-extensions-10-intel-avx10-architecture-specification.html). -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é, 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. +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ées 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ète des ordinateurs parfaitement fonctionnels. Cela permet aussi d'activer ou de désactuver des fonctions optimisées à l'exécution. C'est l'un des plus grands avantages de l'open source. +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'instructions 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'instructions 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 agé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 captable de vous indiquer si une instruction n'est pas disponible dans un jeu d'instruction particulier. +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'instructions **SSE**, machines qui datent d'il y a 25 ans. +Heureusement, **x86inc.asm** est capable de vous indiquer si une instruction n'est pas disponible dans un jeu d'instructions 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): +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é | +| Jeu d'instructions | Disponibilité | | :---- | :---- | | SSE2 | 100% | | SSE3 | 100% | @@ -37,13 +37,13 @@ Pour vous donner une idée des compatibilités, voici la disponibilité des jeux | 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ésent 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. +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 se casse. -Inter fournit un manuel de jeu d'instruction 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) +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/) +Cela peut être laborieux de chercher dans 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/) +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'utilsation 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. @@ -51,9 +51,9 @@ Une partie du défi de l'utilsation de l'assembleur x86 est de trouver la bonne 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. +Nous utilisons `ptrdiff_t` pour la déclaration de 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 : +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) @@ -98,13 +98,13 @@ Les chargements sont ensuite effectués avec `widthq` étant négatif. Ainsi, lo 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). +`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 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`. +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 : @@ -118,7 +118,7 @@ Notez que cela aligne uniquement le début de la section RODATA. Des octets de r 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 paquets) et `punpckhbw` (décompacter des octets en mots dans le haut d'un paquets) interviennent. +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 : @@ -144,13 +144,13 @@ 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. +`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```. +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. +`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 @@ -167,13 +167,13 @@ Comme vous pouvez le voir, il y a une instruction supplémentaire par rapport au **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 un byte. Notez que si les valeurs dépassent la plage des bytes, elles seront saturées (c'est-à-dire clampées à la valeur maximale). +`packuswb` (pack unsigned word to byte) et `packsswb` (pack signed word to byte) vous permettent de passer du mot à l'octet. Ils vous permettent d'entrelacer deux registres SIMD contenant des mots en un seul registre SIMD avec un octet. Notez que si les valeurs dépassent la plage des octets, elles seront saturées (c'est-à-dire saturé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. +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) : +Pour chaque octet, l'octet source correspondant est utilisé comme un index dans le registre de destination, sauf lorsque le bit de poids fort (MSB) est activé, l'octet 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 for(int i = 0; i < 16; i++) { @@ -184,7 +184,7 @@ for(int i = 0; i < 16; i++) { } ``` -Voici un exemple simple en assembleur: +Voici un exemple simple en assembleur : ```assembly SECTION_DATA 64 @@ -198,6 +198,6 @@ 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é. +Notez que -1, utilisé comme indice de mélange, facilite la lecture en mettant à zéro l'octet de sortie : `-1` en tant qu'octet est représenté par le champ binaire `0b11111111` (complément à deux), et ainsi le bit de poids fort (MSB) (0x80) est activé. [image1]: \ No newline at end of file