From cfcbefeceefa7cbd7c369357f498ee543a5de0a4 Mon Sep 17 00:00:00 2001 From: Filcent Date: Sat, 9 Aug 2025 15:21:01 +0200 Subject: [PATCH 1/8] Translated README file in italian --- README.it.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 README.it.md diff --git a/README.it.md b/README.it.md new file mode 100644 index 0000000..0cc0347 --- /dev/null +++ b/README.it.md @@ -0,0 +1,20 @@ +Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti faranno aprire gli occhi a cosa sta effeivamente succedendo all'interno del tuo computer. + +**Conoscenze Richieste** + +* Conoscenza del C, in particolare dei puntatori. Se non sei familiare con il C, leggi e studia [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) +* Matematica delle superiori (scalare vs vettore, addizione, moltiplicazione, ecc.) + +**Lezioni** + +In questa repository Git ci sono lezioni e compiti (non ancora caricati) in corrispondenza ad ogni lezione. Alla fine delle lezioni sarai in grado di contribuire a FFmpeg + +Un server discord è disponibile per rispondere a domande: +https://discord.com/invite/Ks5MhUhqfB + +**Traduzioni** + +* [English](./README.md) +* [Français](./README.fr.md) +* [Spanish](./README.es.md) + From c196674b9b31647c65415c1aaabc8ece994538d8 Mon Sep 17 00:00:00 2001 From: Filcent Date: Sat, 9 Aug 2025 19:24:05 +0200 Subject: [PATCH 2/8] Translated lesson 1 and fixed link in README --- README.it.md | 2 +- lesson_01/index.it.md | 217 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 lesson_01/index.it.md diff --git a/README.it.md b/README.it.md index 0cc0347..edd3cdb 100644 --- a/README.it.md +++ b/README.it.md @@ -10,7 +10,7 @@ Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo i In questa repository Git ci sono lezioni e compiti (non ancora caricati) in corrispondenza ad ogni lezione. Alla fine delle lezioni sarai in grado di contribuire a FFmpeg Un server discord è disponibile per rispondere a domande: -https://discord.com/invite/Ks5MhUhqfB +[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) **Traduzioni** diff --git a/lesson_01/index.it.md b/lesson_01/index.it.md new file mode 100644 index 0000000..c24ef14 --- /dev/null +++ b/lesson_01/index.it.md @@ -0,0 +1,217 @@ +**Linguaggio Assembly FFmpeg Lezione Uno** + +**Introduzione** + +Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti faranno aprire gli occhi a cosa sta effeivamente succedendo all'interno del tuo computer. + +**Conoscenze Richieste** + +* Conoscenza del C, in particolare dei puntatori. Se non sei familiare con il C, leggi e studia [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) +* Matematica delle superiori (scalare vs vettore, addizione, moltiplicazione, ecc.) + +**Cos'è il linguaggio assembly?** + +Il linguaggio assembly è un linguaggio di programmazione dove il codice che scrivi corrisponde alle istruzioni che una CPU processa. Il linguaggio assembly leggibile dagli umani viene, come suggerito dal nome, *assemblato* in dati binari, conosciuti come *codice macchina*, comprensibile dalla CPU. Potresti vedere il linguaggio assembly riferito come "assemblatore", abbreviato in "asm". + +La gran maggioranza del codice di FFmpeg è ciò che è noto come *SIMD, Single Instuction Multiple Data (Istruzione Singola Dati Muntipli)*. SIMD è a volte denominata programmazione vettoriale. La maggior parte dei linguaggi di programmazione operano su un elemento di dati alla volta, conosciuto come programmazione scalare. + +Come avrai potuto intuire, SIMD si presta bene nel processare immagini, video ed audio in quanto hanno molti dati ordinati sequenzialmente in memoria. Ci sono istruzioni specializzate disponibili nella CPU per aiutarci processare dati in sequenza. + +In FFmpeg, vedrai i termini "funzione assembly", "SIMD", e "vettorizzare" usati intercambiabilmente. Fanno tutti riferimento alla stessa cosa: Scrivere una funzione nel linguaggio assembly a mano per processare più elementi di dati in una sola volta. Alcuni progetti potrebbero far riferimento a questi come "nuclei dell' assemblatore". + +Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, degli studenti delle superiori hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effeivamente imparare. + +**Perchè scrivamo in linguaggio assembly?** +Per rendere l'elaborazione multimediale veloce. È molto comune raggiungere un miglioramento di velocità anche di 10 o più volte scrivendo codice assembly, il che è specialmente importante per riprodurre video in tempo reale senza interruzioni. Oltretutto, ciò risparmia energia ed estende l'autonomia delle batterie. È giusto evidenziare che funzioni di codifica e decodifica video sono alcune delle più usate nel mondo, sia da utenti finali che da grandi compagnie e datacenter. Perciò anche un piccolo miglioramento si accumula velocemente. + +Online, vedrai spesso che le persone usano *intrinseche*, ovvero funzioni simili al C che mappano ad istruzioni assembly, permettendo uno sviluppo più veloce. In FFmpeg non usiamo intrinseche, ma piuttosto scriviamo codice assembly a mano. Questa rappresenta un area controversa, ma le intrinseche sono solitamente più lente del 10-15% rispetto all'assembly a mano (coloro che supportano le intrinseche non sarebbero d'accordo), in base al compilatore. Per FFmpeg, ogni piccolo miglioramento di velocità aiuta, e per questo scriviamo in codice assembly direttamente. C'è anche chi sostiene che le intrinseche siano difficili da leggere a causa dell'uso della "[Notazione Ungara](https://it.wikipedia.org/wiki/Notazione_ungara)" + +Potresti anche vedere *assembly in linea* (ovvero senza usare intrinseche) da qualche parte in FFmpeg per ragioni storiche, o in progetti come il Kernel Linux a causa di casi d'uso molto specifici. Questo è quando il codice assembly non è in un file separato, ma scritto in linea con codice C. L'opinione prevalente in progetti come FFmpeg è che questo codice è difficile da leggere, non ampiamente supportato da compilatori ed immantenibile. + +Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che nulla di tutto ciò è necessario e che il compilatore può fare tutta questa "vettorizzazione" al posto tuo. Almeno per lo scopo di imparare, ignorali. test recenti, ad esempio nel [the dav1d project](https://www.videolan.org/projects/dav1d.html) mostrano una velocizzazione circa di 2 volte grazie a questa vettorizzazione automatica, mentre le versioni scritte a mano riuscivano a raggiungere le 8 volte. + +**Gusti del linguaggio assembly** + +Queste lezioni si concentreranno sull'assembly x86 a 64 bit. Questo è anche conosciuto come amd64, anche se funziona lo stesso su CPU Intel. Ci sono altri tipi di assembly per altre CPU, come ARM e RISC-V e potenzialmente nel futuro queste lezioni saranno estese per coprire tali. + +Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo la sintasi Intel. + +**Materiali di supporto** +Potresti sorprenderti al fatto che libri o risorse in rete come Stack Overflow non sono particolarmente utili come riferimenti. Questo è in parte a causa della nostra scelta di scrivere codice assembly a mano con sintassi Intel. Ma è anche perchè molte risorse in rete si concentrano alla programmazione di sistemi operativi o alla programmazione hardware, solitamente usando codice non-SIMD. L'assembly FFmpeg è particolarmente concentrato alla processazione di immagini ad alte prestazioni, e come vedrai è un approccio particolarmente unico alla programmazione in assembly. Detto ciò, sarà facile comprendere altri casi d'uso dell'assembly una volta che avrai completato queste lezioni. + +Molti libri vanno molto sui dettagli dell'architettura del computer prima di insegnare l'assembly. Questo va bene se vuoi imparare quello, ma dal nostro punto di vista, è come studiare come funziona un motore prima di imparare a guidare. + +Detto ciò, i diagrammi nelle parti verso la fine del libro "The Art of 64-bit assembly" che mostrano istruzioni SIMD ed il loro comportamento in modo visuale sono utili: [https://artofasm.randallhyde.com/](https://artofasm.randallhyde.com/) + +Un server discord è disponibile per rispondere a domande: +[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) + +**Registri** +I registri sono aree nella CPU dovev i dati possono essere processati. Le CPU non poerano direttamente sulla memoria, ma invece i dati sono caricati nei registri, processari e scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. + +**Registri a Scopo Generale** +Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono conterere dati, in questo caso un valore a 64 bit, o un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. + +nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scitto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e vengono astratte. + +**Registri vettoriali** +I registri vettoriali (SIMD), come suggerisce il nome, contengono più elementi di dati. Ci sono vari tipi di registri vettoriali: + +* Registri mm - Registri MMX, grandi 64 bit, storici ed ora non usati più di tanto +* Registri xmm - Registri XMM, grandi 128 bit, ad ampia disposizione +* Registri ymm - Registri YMM, grandi 256 bit, ci sono alcune complicazioni usando questi +* Registri ZMM - Registri ZMM, grandi 512 bit, a disponibilità limitata + +La maggior parte dei calcoli nella compressione e decompressione video sono basati su numeri interi, quindi ci atterremo a questi. Ecco un esempio di 16 byte in un registro xmm: + + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Ma potrebbero essere otto parole (interi a 16 bit) + +| a | b | c | d | e | f | g | h | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +O quattro doppie parole (interi a 32 bit) + +| a | b | c | d | +| :---- | :---- | :---- | :---- | + +O due parole quadruple (interi a 64 bit) + +| a | b | +| :---- | :---- | + +Riassumendo: + +* byte - dati a 8 bit +* parole (**w**ords) - dati a 16 bit +* doppie parole (**d**oublewords) - dati a 32 bit +* parole quadruple (**q**uadwords) - dati a 64 bit +* doppie parole quadruple (**d**ouble **q**uadwords) - dati a 128 bit + +I caratteri in grassetto saranno importanti dopo. + +**x86inc.asm include** +Vedrai come in molti esempi includeremo il file x86inc.asm. x86inc.asm è un livello d'astrazione leggero usato in FFmpeg, x264 e dav1d per rendere la vita di un programmatore d'assembly più semplice. Aiuta in molti modi, ma tanto per iniziare, una delle cose che fa è etichettare i GPR, r0, r1, r2. Questo significa che non dovrai ricordarti i nomi dei registri. Come menzionato prima, i GPR sono generalmente solo impalcature quindi questo ci semplifica la vita. + +**Un semplice estratto du asm scalare** + +Diamo un occhio a questo estratto semplice (e molto artificiale) di asm scalare (codice assembly che opera su elementi di dati individuali, uno alla volta, all'interno di ogni Istruzione) per vedere che sta succedendo: + +```assembly +mov r0q, 3 +inc r0q +dec r0q +imul r0q, 5 +``` + +nella prima linea, il *valore immediato* 3 (un valore memorizzato direttamente nel codice assembly stesso, opposto ad un valore recuperato dalla memoria) è memorizzato nel registro r0 come parola quadrupla. Da notare che nella sintassi Intel, l'operando di origine (il valore o il luogo che fornisce i dati, trovatosi alla destra) è trasferito all'operando di destinazione (il luogo ricevente i dati, trovatosi alla sinistra), simile al comportamento di memcpy. Può anche assere letto come "r0q = 3", dato che l'ordine è lo stesso. il suffisso di r0 "q" designa il registro ad essere usato come parola quadrupla. inc incrementa il valore facendo in modo che r0q contenga 4, dec decrementa il valore nuovamente a 3. imul moltiplica il valore per 5. Quindi alla fine, r0q contiene 15. + +È da notare che le istruzioni leggibili dagli umani come mov e inc, assemblate poi in codice macchina dall'assembler, sono chiamate *mnemoniche*. Potresti vedere in rede e su libri mnemoniche rappresentate in maiuscolo con lettere come MOV e INC ma queste sono le stesse delle loro versioni in minuscolo. In FFmpeg, utiliziamo mnemoniche in minuscolo e teniamo il maiuscolo riservato per le macro. + +**Comprendere una funzione vettoriale di base** + +Ecco la nostra prima funzione 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 +``` + +Attraversiamolo linea per linea: + +```assembly +%include "x86inc.asm" +``` + +Questa è una "intestazione" sviluppata nelle comunità di x264, FFmpeg e dav1d per dare aiutanti, nomi e macro predefiniti (come cglobal sotto) per semplificare la scrittura dell'assembly. + +```assembly +SECTION .text +``` + +Questo denota la sezione dove è piazzato il codice che si vuole eseguire. Questo è in contrasto alla sezione .data, dove possono essere inseriti i dati costanti. + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +``` + +La prima riga è un commento (il punto e virgola ";" in asm è come "//" in C) che ci mostra come l'argomento della funzione appare in C. La seconda riga ci mostra come stiamo inizializzando la funzione per usare i registri XMM, usando il set di istruzioni sse2. Questo perchè paddb è un istruzione sse2. Copriremo l'sse2 in ulteriori dettagli nella prossima lezione. + +```assembly +cglobal add_values, 2, 2, 2, src, src2 +``` + +Questa è una riga importante in quanto definisce una funzione C di nome "add_values". + +Attraversiamo ogni elemento uno per volta: + +* Il parametro dopo mostra cbe ha due argomenti della funzione. +* Il parametro successivo mostra che useremo due GPR come argomenti. In alcuni casi potremo voler usare più GPR, quindi dovremo dire a x86util che ce ne servono di più. +* Il parametro successivo dice a x86util quanti registri XMM useremo. +* I due parametri dopo sono le etichette per gli argomenti della funzione. + +Vale la pena notare che codice più vecchio potrebbe non avere etichette per gli argomenti della funzione ma invece potrebbe riferirsi ai GPR usando direttamente r0, r1 ecc. + +```assembly + movu m0, [srcq] + movu m1, [src2q] +``` + +movu è l'abbreviazione di movdqw (move double quad unaligned, sposta doppia quadrupla non allineata). Parleremo dell'allineamento in un'altra lezione, ma per ore movu può essere trattato come uno spostamento a 128 bit da [srcq]. Nel caso di mov, le parentesi quadre indicano che l'indirizzo in [srcq] sta venendo dereferenziato, l'equivalente di **src in C.* Questo è conosciuto come un caricamento (load). Da notare che il suffisso "q" si riferisce alla dimensione del puntatore *(*ovvero in C rappresenta *sizeof(*src) == 8 su sistemi a 64 bit, e x86asm è abbastanza intelligente da usare 32 bit su sistemi a 32 bit) ma l'operazione di caricamento sottostante rimane a 128 bit. + +È da notare come non ci riferiamo ai registri vettoriali con il loro nome intero, in questo caso xmm0, ma come m0, una forma astratta. Nelle lezioni future vedremo come ciò significa sia possibile scrivere codice una volta sola e fare in modo funzioni su varie dimensioni di registri SIMD. + + +```assembly +paddb m0, m1 +``` + +paddb (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni registro come mostrato qui sotto. Il prefisso "P" sta per "Packed" (Impaccheggiato) ed è usato per identificare istruzioni vettoriali vs istruzioni scalari. + + + +| 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 +``` + +Questo è noto come un immagazzinaggio (*store*). I dati sono scritti all'indirizzo nel puntatore srcq. + +```assembly +RET +``` + +Questa è una macro per denotare il ritorno della funzione. Praticamente tutte le funzioni in FFmpeg modificano i dati negli argomenti invece che far ritornare un valore. + +Come vedrai nel compito, creeremo puntatori di funzione a funzioni assembly e useremo questi dove possiamo + +[Next Lesson](../lesson_02/index.it.md) From 0fb941203874cf13ebd969bfb03ac1a60b092bae Mon Sep 17 00:00:00 2001 From: Filcent Date: Sat, 9 Aug 2025 19:28:52 +0200 Subject: [PATCH 3/8] Added links to translation in READMEs --- README.es.md | 3 ++- README.fr.md | 3 ++- README.md | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.es.md b/README.es.md index 7c9a91a..add1934 100644 --- a/README.es.md +++ b/README.es.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Traducciones** * [English](./README.md) -* [Français](./README.fr.md) \ No newline at end of file +* [Français](./README.fr.md) +* [Italian](./README.it.md) diff --git a/README.fr.md b/README.fr.md index b9b1fa8..fd3808e 100644 --- a/README.fr.md +++ b/README.fr.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Traductions** * [English](./README.md) -* [Spanish](./README.es.md) \ No newline at end of file +* [Spanish](./README.es.md) +* [Italian](./README.it.md) diff --git a/README.md b/README.md index 67c4007..66fa6e3 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Translations** * [Français](./README.fr.md) -* [Spanish](./README.es.md) \ No newline at end of file +* [Spanish](./README.es.md) +* [Italian](./README.it.md) From 8f3b72b3a36e0cfa1b3aac1e48b38f4fc1ba6200 Mon Sep 17 00:00:00 2001 From: Filcent Date: Sat, 9 Aug 2025 19:48:17 +0200 Subject: [PATCH 4/8] Fixed grammar, spelling errors and typos in lesson 1 --- lesson_01/index.it.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lesson_01/index.it.md b/lesson_01/index.it.md index c24ef14..ffa0a60 100644 --- a/lesson_01/index.it.md +++ b/lesson_01/index.it.md @@ -2,7 +2,7 @@ **Introduzione** -Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti faranno aprire gli occhi a cosa sta effeivamente succedendo all'interno del tuo computer. +Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti faranno aprire gli occhi a cosa sta effettivamente succedendo all'interno del tuo computer. **Conoscenze Richieste** @@ -13,20 +13,20 @@ Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo i Il linguaggio assembly è un linguaggio di programmazione dove il codice che scrivi corrisponde alle istruzioni che una CPU processa. Il linguaggio assembly leggibile dagli umani viene, come suggerito dal nome, *assemblato* in dati binari, conosciuti come *codice macchina*, comprensibile dalla CPU. Potresti vedere il linguaggio assembly riferito come "assemblatore", abbreviato in "asm". -La gran maggioranza del codice di FFmpeg è ciò che è noto come *SIMD, Single Instuction Multiple Data (Istruzione Singola Dati Muntipli)*. SIMD è a volte denominata programmazione vettoriale. La maggior parte dei linguaggi di programmazione operano su un elemento di dati alla volta, conosciuto come programmazione scalare. +La gran maggioranza del codice di FFmpeg è ciò che è noto come *SIMD, Single Instruction Multiple Data (Istruzione Singola Dati Multipli)*. SIMD è a volte denominata programmazione vettoriale. La maggior parte dei linguaggi di programmazione operano su un elemento di dati alla volta, conosciuto come programmazione scalare. -Come avrai potuto intuire, SIMD si presta bene nel processare immagini, video ed audio in quanto hanno molti dati ordinati sequenzialmente in memoria. Ci sono istruzioni specializzate disponibili nella CPU per aiutarci processare dati in sequenza. +Come avrai potuto intuire, SIMD si presta bene nel processare immagini, video ed audio in quanto hanno molti dati ordinati sequenzialmente in memoria. Ci sono istruzioni specializzate disponibili nella CPU per aiutarci a processare dati in sequenza. In FFmpeg, vedrai i termini "funzione assembly", "SIMD", e "vettorizzare" usati intercambiabilmente. Fanno tutti riferimento alla stessa cosa: Scrivere una funzione nel linguaggio assembly a mano per processare più elementi di dati in una sola volta. Alcuni progetti potrebbero far riferimento a questi come "nuclei dell' assemblatore". -Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, degli studenti delle superiori hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effeivamente imparare. +Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, degli studenti delle superiori hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effettivamente imparare. -**Perchè scrivamo in linguaggio assembly?** +**Perchè scriviamo in linguaggio assembly?** Per rendere l'elaborazione multimediale veloce. È molto comune raggiungere un miglioramento di velocità anche di 10 o più volte scrivendo codice assembly, il che è specialmente importante per riprodurre video in tempo reale senza interruzioni. Oltretutto, ciò risparmia energia ed estende l'autonomia delle batterie. È giusto evidenziare che funzioni di codifica e decodifica video sono alcune delle più usate nel mondo, sia da utenti finali che da grandi compagnie e datacenter. Perciò anche un piccolo miglioramento si accumula velocemente. Online, vedrai spesso che le persone usano *intrinseche*, ovvero funzioni simili al C che mappano ad istruzioni assembly, permettendo uno sviluppo più veloce. In FFmpeg non usiamo intrinseche, ma piuttosto scriviamo codice assembly a mano. Questa rappresenta un area controversa, ma le intrinseche sono solitamente più lente del 10-15% rispetto all'assembly a mano (coloro che supportano le intrinseche non sarebbero d'accordo), in base al compilatore. Per FFmpeg, ogni piccolo miglioramento di velocità aiuta, e per questo scriviamo in codice assembly direttamente. C'è anche chi sostiene che le intrinseche siano difficili da leggere a causa dell'uso della "[Notazione Ungara](https://it.wikipedia.org/wiki/Notazione_ungara)" -Potresti anche vedere *assembly in linea* (ovvero senza usare intrinseche) da qualche parte in FFmpeg per ragioni storiche, o in progetti come il Kernel Linux a causa di casi d'uso molto specifici. Questo è quando il codice assembly non è in un file separato, ma scritto in linea con codice C. L'opinione prevalente in progetti come FFmpeg è che questo codice è difficile da leggere, non ampiamente supportato da compilatori ed immantenibile. +Potresti anche vedere *assembly in linea* (ovvero senza usare intrinseche) da qualche parte in FFmpeg per ragioni storiche, o in progetti come il Kernel Linux a causa di casi d'uso molto specifici. Questo è quando il codice assembly non è in un file separato, ma scritto in linea con codice C. L'opinione prevalente in progetti come FFmpeg è che questo codice è difficile da leggere, non ampiamente supportato da compilatori e non manutenibile. Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che nulla di tutto ciò è necessario e che il compilatore può fare tutta questa "vettorizzazione" al posto tuo. Almeno per lo scopo di imparare, ignorali. test recenti, ad esempio nel [the dav1d project](https://www.videolan.org/projects/dav1d.html) mostrano una velocizzazione circa di 2 volte grazie a questa vettorizzazione automatica, mentre le versioni scritte a mano riuscivano a raggiungere le 8 volte. @@ -34,7 +34,7 @@ Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che null Queste lezioni si concentreranno sull'assembly x86 a 64 bit. Questo è anche conosciuto come amd64, anche se funziona lo stesso su CPU Intel. Ci sono altri tipi di assembly per altre CPU, come ARM e RISC-V e potenzialmente nel futuro queste lezioni saranno estese per coprire tali. -Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo la sintasi Intel. +Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo la sintassi Intel. **Materiali di supporto** Potresti sorprenderti al fatto che libri o risorse in rete come Stack Overflow non sono particolarmente utili come riferimenti. Questo è in parte a causa della nostra scelta di scrivere codice assembly a mano con sintassi Intel. Ma è anche perchè molte risorse in rete si concentrano alla programmazione di sistemi operativi o alla programmazione hardware, solitamente usando codice non-SIMD. L'assembly FFmpeg è particolarmente concentrato alla processazione di immagini ad alte prestazioni, e come vedrai è un approccio particolarmente unico alla programmazione in assembly. Detto ciò, sarà facile comprendere altri casi d'uso dell'assembly una volta che avrai completato queste lezioni. @@ -47,12 +47,12 @@ Un server discord è disponibile per rispondere a domande: [https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) **Registri** -I registri sono aree nella CPU dovev i dati possono essere processati. Le CPU non poerano direttamente sulla memoria, ma invece i dati sono caricati nei registri, processari e scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. +I registri sono aree nella CPU dove i dati possono essere processati. Le CPU non eseguiranno operazioni direttamente sulla memoria, ma invece i dati verrano prima caricati nei registri, processati e poi scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. **Registri a Scopo Generale** -Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono conterere dati, in questo caso un valore a 64 bit, o un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. +Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono contenere dati, in questo caso un valore a 64 bit, o un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. -nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scitto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e vengono astratte. +nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scritto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e vengono astratte. **Registri vettoriali** I registri vettoriali (SIMD), come suggerisce il nome, contengono più elementi di dati. Ci sono vari tipi di registri vettoriali: @@ -109,7 +109,7 @@ imul r0q, 5 nella prima linea, il *valore immediato* 3 (un valore memorizzato direttamente nel codice assembly stesso, opposto ad un valore recuperato dalla memoria) è memorizzato nel registro r0 come parola quadrupla. Da notare che nella sintassi Intel, l'operando di origine (il valore o il luogo che fornisce i dati, trovatosi alla destra) è trasferito all'operando di destinazione (il luogo ricevente i dati, trovatosi alla sinistra), simile al comportamento di memcpy. Può anche assere letto come "r0q = 3", dato che l'ordine è lo stesso. il suffisso di r0 "q" designa il registro ad essere usato come parola quadrupla. inc incrementa il valore facendo in modo che r0q contenga 4, dec decrementa il valore nuovamente a 3. imul moltiplica il valore per 5. Quindi alla fine, r0q contiene 15. -È da notare che le istruzioni leggibili dagli umani come mov e inc, assemblate poi in codice macchina dall'assembler, sono chiamate *mnemoniche*. Potresti vedere in rede e su libri mnemoniche rappresentate in maiuscolo con lettere come MOV e INC ma queste sono le stesse delle loro versioni in minuscolo. In FFmpeg, utiliziamo mnemoniche in minuscolo e teniamo il maiuscolo riservato per le macro. +È da notare che le istruzioni leggibili dagli umani come mov e inc, assemblate poi in codice macchina dall'assembler, sono chiamate *mnemoniche*. Potresti vedere in rete e su libri mnemoniche rappresentate in maiuscolo con lettere come MOV e INC ma queste sono le stesse delle loro versioni in minuscolo. In FFmpeg, utilizziamo mnemoniche in minuscolo e teniamo il maiuscolo riservato per le macro. **Comprendere una funzione vettoriale di base** @@ -133,7 +133,7 @@ cglobal add_values, 2, 2, 2, src, src2 RET ``` -Attraversiamolo linea per linea: +Attraversiamo il codice linea per linea: ```assembly %include "x86inc.asm" @@ -183,7 +183,7 @@ movu è l'abbreviazione di movdqw (move double quad unaligned, sposta doppia qua paddb m0, m1 ``` -paddb (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni registro come mostrato qui sotto. Il prefisso "P" sta per "Packed" (Impaccheggiato) ed è usato per identificare istruzioni vettoriali vs istruzioni scalari. +paddb (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni registro come mostrato qui sotto. Il prefisso "P" sta per "Packed" (Impacchettato) ed è usato per identificare istruzioni vettoriali vs istruzioni scalari. From 1e6d8711eb551e34c83d7599bc43ce3b6eaf903e Mon Sep 17 00:00:00 2001 From: Filcent Date: Sun, 10 Aug 2025 17:59:35 +0200 Subject: [PATCH 5/8] Translated lesson 2 to italian --- lesson_02/index.it.md | 168 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 lesson_02/index.it.md diff --git a/lesson_02/index.it.md b/lesson_02/index.it.md new file mode 100644 index 0000000..525ab87 --- /dev/null +++ b/lesson_02/index.it.md @@ -0,0 +1,168 @@ +**Lezione Linguaggio Assembly FFmpeg Due** + +Ora che hai scritto la tua prima funzione in linguaggio assembly, introdurremo rami e cicli. + +Dobbiamo prima introdurre l'idea di etichette e salti. Nell'esempio artificiale qui sotto, l'istruzione jmp sposta l'istruzione del codice dopo ".loop:". ".loop:" è conosciuta come un'*etichetta*, il fatto che sia prefissata dal punto indica che è un *etichetta locale*, il che effettivamente ti permette di riusare lo stesso nome dell'etichetta attraverso varie funzioni. Questo esempio, ovviamente, mostra un ciclo infinito, ma estenderemo quest'ultimo più tardi a qualcosa di più realistico. + +```assembly +mov r0q, 3 +.loop: + dec r0q + jmp .loop +``` + +Prima di creare un ciclo realistico dobbiamo introdurre il registro *FLAGS* (Bandiere). Non ci soffermeremo troppo sulle complessità di *FLAGS*, (in quanto, di nuovo, le operazioni sui GPR fanno per la maggior parte da impalcatura) ma ci sono varie flags come la Zero-flag, la Sign-flag e la Overflow-flag che sono alzate o abbassate in base al risultato della maggior parte delle operazioni non-mov su dati scalari come operazioni aritmetiche e spostamenti. + +Ecco un esempio dove il contatore del ciclo conta alla rovescia fino allo zero e jg (*jump if greater than zero*, salta se più di zero) è la condizione del ciclo. dec r0q imposta il valore delle FLAGs in base al valore di r0q dopo l'istruzione, e puoi quindi saltare in base a loro. + +```assembly +mov r0q, 3 +.loop: + ; fai qualcosa + dec r0q + jg .loop ; salta se più grande di zero +``` + +Questo è equivalente al seguente codice C: + +```c +int i = 3; +do +{ + // fai qualcosa + i--; +} while(i > 0) +``` + +Questo codice C è un po' insolito. Solitamente, un ciclo in C è scritto in questo modo: + +```c +int i; +for(i = 0; i < 3; i++) { + // fai qualcosa +} +``` + +Questo è grossolanamente uguale a (non c'è un modo smplice di paragonare questo ciclo ```for``` ) + +```assembly +xor r0q, r0q +.loop: + ; fai qualcosa + inc r0q + cmp r0q, 3 + jl .loop ; salta se (r0q - 3) < 0, ovvero (r0q < 3) +``` + +Ci sono alcune cose da notare in questo frammento di codice. Prima tra tutte è ```xor r0q, r0q``` ovvero un modo usuale per impostare un registro a zero, il chè in alcuni sistemi è più veloce di ```mov r0q, 0```, in quanto, semplicemente, non si sta effettivamente caricando nulla. Può anche essere usato su registri SIMD attraverso ```pxor m0, m0``` per azzerare un intero registro. La prossima cosa da notare è l'uso di cmp. Praticamente, cpm sottrae il secondo registro dal primo (senza memorizzare il risultato da nessuna parte) e imposta *FLAGS*, ma come scritto nel commento, può essere letto insieme al salto, (jl = *jump if less than zero*, salta se meno di zero) per saltare se ```r0q < 3```. + +Da notare come c'è un istruzione in più in questo frammento. Generalmente, meno istruzioni significano codice più veloce, il che è il motivo per cui il frammento precedente è preferibile a questo. Come vedrai nelle lezioni future, ci sono più trucchi per evitare di scrivere istruzioni extra e avere *FLAGS* impostato da aritmetica o da altre operazioni. Da notare anche come non stiamo scrivendo assembly per corrispondere esattamente a cicli in C, ma stiamo scrivendo i cicli facendo in modo che siano il più veloce possibile in assembly. + +A seguito alcune mnemoniche per i salti che andrai ad usare (ci sono anche *FLAGS* per completezza, ma non devi saperne le specifiche per scrivere cicli): + +| Mnemonica | Descrizione | FLAGS | +| :---- | :---- | :---- | +| JE/JZ | Salta se uguale/Zero | ZF = 1 | +| JNE/JNZ | Salta se Non uguale/Non zero | ZF = 0 | +| JG/JNLE | Salta se maggiore/non minore o uguale (con segno) | ZF = 0 and SF = OF | +| JGE/JNL | Salta se maggiore o uguale/Non minore (con segno) | SF = OF | +| JL/JNGE | Salta se minore/Non maggiore o uguale (con segno) | SF ≠ OF | +| JLE/JNG | Salta se non minore o uguale/Not maggiore (con segno) | ZF = 1 or SF ≠ OF | + +**Costanti** + +Vediamo degli esempi che ci mostrano come usare le costanti: + +```assembly +SECTION_RODATA + +constants_1: db 1,2,3,4 +constants_2: times 2 dw 4,3,2,1 +``` + +* SECTION_RODATA specifica che questa è una sezione di dati a sola lettura (*Read Only Data*). (Questa è una macro siccome i differenti formati di file di output usati dai sistemi operativi dichiarano questo in modo differente) +* constants\_1: L'etichetta constants\_1 è definita come ```db``` (dichiara byte) - è equivalente a uint8\_t constants\_1\[4\] = {1, 2, 3, 4}; +* constants\_2: Questa usa la macro ```times 2``` per ripetere le parole dichiarate - è equivalente a uint16\_t constants\_2\[8\] = {4, 3, 2, 1, 4, 3, 2, 1}; + +Questa etichette, convertite poi in un indirizzo di memoria dall'assembler, possono essere usate in caricamenti (ma non scritture, in quanto sono a sola lettura). Alcune istruzioni prendono come operando un indirizzo di memoria quindi possono essere usate senza caricamenti espliciti in un registro (ci sono pro e contro nel fare questo). + +**Offsets** (Sfalsamenti) + +Gli sfalsamenti sono la distanza (in byte) tra elementi consecutivi in memoria Lo sfalsamento è determinato dalla **dimensione di ogni elemento** nella struttura di dati. + +Ora che sei in grado di scrivere cicli, è arrivata l'ora di andare a recuperare dati. Ci sono però alcune differenze rispetto al C. Osserviamo il seguente ciclo in C: + +```c +uint32_t data[3]; +int i; +for(i = 0; i < 3; i++) { + data[i]; +} +``` + +Lo sfalsamento di 4 byte tra gli elementi di data è precalcolato dal compilatore C. Quando però si scrive assembly a mano, bisogna anche calcolare lo sfalsamento a mano. + +Guardiamo alla sintassi per il calcolo dell'indirizzo di memoria. Questo si applica a tutti i tipi di indirizzi di memoria. + +```assembly +[base + scala*indice + spiazzamento] +``` + +* base - Questo è un GPR (solitamente un puntatore da un argomento di una funzione C) +* scala - Questa può essere 1, 2, 4, 8. 1 è il predefinito +* indice - Questo è un GPR (solitamente un contatore di ciclo) +* spiazzamento - Questo è un numero intero (fino a 32 bit). Lo spiazzamento è uno sfalsamento all'intero dei dati + +x86asm provvede la costante mmsize, la quale ti fa sapere la dimensione del registro SIMD con cui stai lavorando. + +Ecco un esempio semplice (e insensato) per illustrare il caricamento da sfalsamenti personalizzati: + +```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] + + ; fai qualcosa + + add srcq, mmsize +dec r1q +jg .loop + +RET +``` + +Da notare come su ```movu m1, [srcq+2*r1q+3+mmsize]``` l'assembler pre-calcolerà lo spiazzamento costante corretto da usare. Nella prossima lezione ti mostreremo un trucchetto per evitare il fare un add e un dec nel ciclo, rimpiazzandoli con un singolo add. + +**LEA** + +Ora che comprendi gli sfalsamenti sarai in grado di usare lea (*Load Effective Address*, Carica Indirizzo Effettivo). Questo ti permette di eseguire moltiplicazione e addizione in una sola istruzione, che sarà più veloce rispetto all'uso di più istruzioni. Ci sono, ovviamente, limitazioni per cosa puoi effettivamente moltiplicare ed aggiungere ma ciò non influisce sul fatto che lea sia un istruzione molto potente. + +```assembly +lea r0q, [base + scale*index + disp] +``` + +Contrario al suo nome, LEA può essere usato per normale aritmetica e calcolo d'indirizzo. Puoi fare qualcosa tanto complicato quanto: + +```assembly +lea r0q, [r1q + 8*r2q + 5] +``` + +È da notare come questo non abbia effetto sui contenuti di r1q e r2q. Non ha effetto nemmeno su *FLAGS* (quindi non puoi saltare in base al risultato dell'operazione). Usando LEA si evitano tutte queste istruzioni e registri tempranei (questo codice non è equivalente in quanto add cambia *FLAGS*) + +```assembly +movq r0q, r1q +movq r3q, r2q +sal r3q, 3 ; shift arithmetic left 3 = * 8 +add r3q, 5 +add r0q, r3q +``` + +Vedrai come lea è molto usato per impostare indirizzi prima di cicli o per eseguire calcoli come qui sopra. Ovviamente è da notare che non è possibile eseguire ogni tipo di moltiplicazione e addizione, ma moltiplicazioni per 1, 2, 4, 8 e addizioni di uno sfalsamento fisso sono comuni. + +Nel compito dovrai caricare una costante ed addizionare i valori ad un vettore SIMD in un ciclo + +[Prossima Lezione](/lesson_03/index.it.md) From 772b19467509c8d04c8815a073d65c145bf4023f Mon Sep 17 00:00:00 2001 From: Filcent <157149162+Filcent@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:06:12 +0000 Subject: [PATCH 6/8] fixed md in index.it.md Fixed silly markdown errors in lesson 1 --- lesson_01/index.it.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lesson_01/index.it.md b/lesson_01/index.it.md index ffa0a60..f8e0d55 100644 --- a/lesson_01/index.it.md +++ b/lesson_01/index.it.md @@ -21,7 +21,8 @@ In FFmpeg, vedrai i termini "funzione assembly", "SIMD", e "vettorizzare" usati Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, degli studenti delle superiori hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effettivamente imparare. -**Perchè scriviamo in linguaggio assembly?** +**Perchè scriviamo in linguaggio assembly?** + Per rendere l'elaborazione multimediale veloce. È molto comune raggiungere un miglioramento di velocità anche di 10 o più volte scrivendo codice assembly, il che è specialmente importante per riprodurre video in tempo reale senza interruzioni. Oltretutto, ciò risparmia energia ed estende l'autonomia delle batterie. È giusto evidenziare che funzioni di codifica e decodifica video sono alcune delle più usate nel mondo, sia da utenti finali che da grandi compagnie e datacenter. Perciò anche un piccolo miglioramento si accumula velocemente. Online, vedrai spesso che le persone usano *intrinseche*, ovvero funzioni simili al C che mappano ad istruzioni assembly, permettendo uno sviluppo più veloce. In FFmpeg non usiamo intrinseche, ma piuttosto scriviamo codice assembly a mano. Questa rappresenta un area controversa, ma le intrinseche sono solitamente più lente del 10-15% rispetto all'assembly a mano (coloro che supportano le intrinseche non sarebbero d'accordo), in base al compilatore. Per FFmpeg, ogni piccolo miglioramento di velocità aiuta, e per questo scriviamo in codice assembly direttamente. C'è anche chi sostiene che le intrinseche siano difficili da leggere a causa dell'uso della "[Notazione Ungara](https://it.wikipedia.org/wiki/Notazione_ungara)" @@ -30,13 +31,12 @@ Potresti anche vedere *assembly in linea* (ovvero senza usare intrinseche) da qu Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che nulla di tutto ciò è necessario e che il compilatore può fare tutta questa "vettorizzazione" al posto tuo. Almeno per lo scopo di imparare, ignorali. test recenti, ad esempio nel [the dav1d project](https://www.videolan.org/projects/dav1d.html) mostrano una velocizzazione circa di 2 volte grazie a questa vettorizzazione automatica, mentre le versioni scritte a mano riuscivano a raggiungere le 8 volte. -**Gusti del linguaggio assembly** - +**Gusti del linguaggio assembly** Queste lezioni si concentreranno sull'assembly x86 a 64 bit. Questo è anche conosciuto come amd64, anche se funziona lo stesso su CPU Intel. Ci sono altri tipi di assembly per altre CPU, come ARM e RISC-V e potenzialmente nel futuro queste lezioni saranno estese per coprire tali. Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo la sintassi Intel. -**Materiali di supporto** +**Materiali di supporto** Potresti sorprenderti al fatto che libri o risorse in rete come Stack Overflow non sono particolarmente utili come riferimenti. Questo è in parte a causa della nostra scelta di scrivere codice assembly a mano con sintassi Intel. Ma è anche perchè molte risorse in rete si concentrano alla programmazione di sistemi operativi o alla programmazione hardware, solitamente usando codice non-SIMD. L'assembly FFmpeg è particolarmente concentrato alla processazione di immagini ad alte prestazioni, e come vedrai è un approccio particolarmente unico alla programmazione in assembly. Detto ciò, sarà facile comprendere altri casi d'uso dell'assembly una volta che avrai completato queste lezioni. Molti libri vanno molto sui dettagli dell'architettura del computer prima di insegnare l'assembly. Questo va bene se vuoi imparare quello, ma dal nostro punto di vista, è come studiare come funziona un motore prima di imparare a guidare. @@ -46,15 +46,15 @@ Detto ciò, i diagrammi nelle parti verso la fine del libro "The Art of 64-bit a Un server discord è disponibile per rispondere a domande: [https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) -**Registri** +**Registri** I registri sono aree nella CPU dove i dati possono essere processati. Le CPU non eseguiranno operazioni direttamente sulla memoria, ma invece i dati verrano prima caricati nei registri, processati e poi scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. -**Registri a Scopo Generale** +**Registri a Scopo Generale** Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono contenere dati, in questo caso un valore a 64 bit, o un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scritto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e vengono astratte. -**Registri vettoriali** +**Registri vettoriali** I registri vettoriali (SIMD), come suggerisce il nome, contengono più elementi di dati. Ci sono vari tipi di registri vettoriali: * Registri mm - Registri MMX, grandi 64 bit, storici ed ora non usati più di tanto @@ -93,7 +93,7 @@ Riassumendo: I caratteri in grassetto saranno importanti dopo. -**x86inc.asm include** +**x86inc.asm include** Vedrai come in molti esempi includeremo il file x86inc.asm. x86inc.asm è un livello d'astrazione leggero usato in FFmpeg, x264 e dav1d per rendere la vita di un programmatore d'assembly più semplice. Aiuta in molti modi, ma tanto per iniziare, una delle cose che fa è etichettare i GPR, r0, r1, r2. Questo significa che non dovrai ricordarti i nomi dei registri. Come menzionato prima, i GPR sono generalmente solo impalcature quindi questo ci semplifica la vita. **Un semplice estratto du asm scalare** @@ -214,4 +214,4 @@ Questa è una macro per denotare il ritorno della funzione. Praticamente tutte l Come vedrai nel compito, creeremo puntatori di funzione a funzioni assembly e useremo questi dove possiamo -[Next Lesson](../lesson_02/index.it.md) +[Prossima Lezione](../lesson_02/index.it.md) From eb3a1bb2653da74eab31997dac5957bf20affd55 Mon Sep 17 00:00:00 2001 From: Filcent Date: Mon, 11 Aug 2025 14:52:04 +0200 Subject: [PATCH 7/8] translated lesson 3 + fixed broken link --- lesson_02/index.it.md | 2 +- lesson_03/index.it.md | 203 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 lesson_03/index.it.md diff --git a/lesson_02/index.it.md b/lesson_02/index.it.md index 525ab87..646eeb7 100644 --- a/lesson_02/index.it.md +++ b/lesson_02/index.it.md @@ -165,4 +165,4 @@ Vedrai come lea è molto usato per impostare indirizzi prima di cicli o per eseg Nel compito dovrai caricare una costante ed addizionare i valori ad un vettore SIMD in un ciclo -[Prossima Lezione](/lesson_03/index.it.md) +[Prossima Lezione](../lesson_03/index.it.md) diff --git a/lesson_03/index.it.md b/lesson_03/index.it.md new file mode 100644 index 0000000..e2cbfb3 --- /dev/null +++ b/lesson_03/index.it.md @@ -0,0 +1,203 @@ +**Lezione Assembly FFmpeg Tre** + +Ora spiegheremo un po' di terminologia tecnica e vi daremo una piccola lezione di storia. + +**Set di Istruzioni** + +Come avrei visto nella lezione precedente, abbiamo parlato di SSE2, ovvero un set di istruzioni SIMD. Quando una nuova generazione di CPU viene rilasciata, potrebbe avere nuove istruzioni e a volte una maggiore dimensione dei registri. La storia del set di istruzioni x86 è molto complessa, quindi questa qui sotto è una storia semplificata (ci sono molte più sottocategorie) + +* MMX - Rilasciato nel 1997, primi SIMD nei processori Intel, registri a 64 bit, storico +* SSE (Streaming SIMD Extensions) - Rilasciato nel 1999, registri a 128 bit +* SSE2 - Rilasciato nel 2000, molte nuove istruzioni +* SSE3 - Rilasciato nel 2004, prime istruzioni orizzontali +* SSSE3 (Supplemental SSE3) - Rilasciato nel 2006, nuove istruzioni ma più importante `pshubfb` +* SSE4 - Rilasciato nel 2008, varie nuove istruzioni tra cui minimo e massimo impacchettato +* AVX - Rilasciato nel 2011, registri a 256 bit (solo `float`) a nuova sintassi a tre operandi +* AVX2 - Rilasciato nel 2013, registri a 256 bit per istruzioni con numeri interi +* AVX512 - Rilasciato nel 2017, registri a 512 bit, nuova funzionalità di maschera operativa. Al Questi ebbero uso limitato in FFmpeg a causa della limitazione della frequenza della CPU durante l'utilizzo di nuove funzioni. Completa permutazione a 512 bit con `vpermb`. +* AVX512ICL - Rilasciata nel 2019, rimuove la limitazione della frequenza. +* AVX10 - In arrivo + +È giusto notare che i set di istruzioni possono essere sia rimossi che aggiunti alle CPU. Ad esempio, AVX512 fu [rimosso](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/)), polemicamente, nella 12esima generazione di CPU Intel. Questo è il motivo per cui FFmpeg rileva la CPU su cui sta eseguendo e le capacità di essa all'esecuzione. + +Come avrai potuto osservare nel compito, i puntatori di funzione sono di default in C e sono rimpiazzati con una particolare variante di set di istruzioni. Questo significa che una volta eseguita la rilevazione non servirà più eseguirla. Ciò è in contrasto a ciò che molte applicazioni proprietarie, ovvero codificare un set di istruzioni particolare, rendendo un computer perfettamente operazionale obsoleto. Questo permette anche di usare/non usare funzioni ottimizzate all'esecuzione. Uno dei grandi benefici dell'open source è proprio questo. + +Programmi come FFmpeg vengono usati su miliardi di dispositivi in tutto il mondo, e alcuni di questi potrebbero essere molto vecchi. Tecnicamente, FFmpeg supporta anche macchine che sopportano soltanto SSE, anche se ormai sono al 25esimo compleanno! Fortunatamente x86inc.asm è in grado di comunicarti se stai usando un istruzione che non è disponibile in un particolare set di istruzioni. + +Per dare un idea delle capacità effettive al giorno d'oggi, ecco la disponibilità di set di istruzioni dallo [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) del novembre 2024: + +| Set di Istruzioni | disponibilità | +| :---- | :---- | +| 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% | + +Per un applicazione come FFmpeg con miliardi di utenti, anche lo 0.1% è un gran numero di utenti e bug report se qualcosa va storto. FFmpeg ha un infrastruttura di test molto estensiva per testare le differenze di CPU/SO/Compilatore nella nostra [Suite di test FATE](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Ogni singolo commit è eseguito su centinaia di macchine per essere sicuri nulla si rompa. + +Intel ha un manuale dei set di istruzioni dettagliato qui: +[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) +può essere abbastanza scomodo cercare attraverso un PDF, quindi qui puoi trovare un alternativa web non ufficiale: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/) + +È anche dispoibile una rappresentazione visuale delle istruzioni SIMD qui: [https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/) + +Parte della difficoltà nell'uso dell'assembly x86 sta nel trovare l'istruzione giusta per quello che c'è da fare. In certi casi, le istruzioni possono essere usate in modo diverso da quello originariamente previsto. + +**Trucchetti con lo sfalsamento dei puntatori** + +Torniamo alla nostra funzione originale dalla Lezione 1, ma aggiungiamo un argomento larghezza alla funzione in C. + +Usiamo `ptrdiff_t` per la variabile larghezza invece di `int` per assicurarci che i 32 bit più alti dell'argomento a 64 bit siamo zero. Se avessimo passato direttamente una larghezza `int` nella firma della funzione, e poi avessimo provato ad usarla come `quad` per aritmetica di puntatori (cioè usando `widthq`) i 32 bit più alti del registro possono essere riempiti con valori arbitrari. Potremo risolvere questo usando `movsxd` per estendere il segno (vedi anche la macro `movsxdifnidn` su x86inc.asm), ma questo è un modo più semplice. + +La funzione qui sotto ha il trucchetto dello sfalsamento dei puntatori al suo interno: + +```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 +``` + +Attraversiamola un passo alla volta, in quanto potrebbe fare confusione: + +```assembly + add srcq, widthq + add src2q, widthq + neg widthq +``` + +La larghezza è aggiunta ad ogni puntatore in modo che ogni puntatore ore punti alla fine del buffer che deve essere processato. La larghezza viene poi negata. + +```assembly + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] +``` + +I caricamenti sono eseguiti con `vidthq` negativa. Quindi alla prima iterazione `[srcq.widthq]` punta all'indirizzo di `srcq` originale, ovvero l'inizio del buffer. + +```assembly + add widthq, mmsize + jl .loop +``` + +`mmsize` è aggiunto a `widthq` negativo portandolo più vicino allo zero. La condizione del ciclo ora è `jl` (salta se meno di zero). Questo trucco fa in modo che `widthq` è usato **sia** come offset del puntatore **che** come contatore, risparmiando un'istruzione `cmp`. Questo oltretutto permette sia di usare dello sfalsamento del puntatore in più caricamenti e memorizzazioni che di usare multipli di quel puntatore se necessario (ricordatelo per questo compito). + +**Allineamento** + +In tutti i nostri esempi abbiamo usato `movu` per evitare di avere a che fare con la questione dell'allineamento. Molte CPU possono caricare e memorizzare dati più velocemente se quei dati sono allineati, ovvero se l'indirizzo di memoria è divisibile per la dimensione del registro SIMD. Dove possibile, è buono provare ad usare carichi e memorizzazioni allineate in FFmpeg usando `mova`. + +In FFmpeg, `av_malloc` è in grado di provvedere memoria allineata nell'*heap* e la direttiva preprocessore DECLARE_ALIGNED C ci può dare memoria allineata nello *stack*. Se `mova` è utilizzato con un indirizzo non allineato, causerà un errore di segmentazione e l'applicazione si bloccherà. È anche importante assicurarsi che l'allineamento corrisponda alla dimensione del registro SIMD, quindi 16 per xmm, 32 per ymm e 64 per zmm. + +Ecco come allineiamo l'inizio della sezione RODATA a 64 byte + +```assembly +SECTION_RODATA 64 +``` + +Da notare come questo allinei soltanto l'inizio di RODATA. Byte di riempimento potrebbero essere necessari per essere sicuri che la prossima etichetta rimanga entro il limite di 64 byte. + +**Espansione della portata** + +Un altro argomento che abbiamo evitato finora è stato lo straripamento. Questo succede, ad esempio, quando il valore di un byte va oltre 255 dopo un operazione come un addizione o una moltiplicazione. Potremo voler eseguire un operazione dove ci serve un valore intermedio più grande di un byte (ad esempio una parola), oppure potremo voler lasciare il dato a quel valore intermedio. + +Per byte senza segno, entrano in gioco `punpcklbw` (packed unpack low bytes to words) e `punpckhbw` (packed unpack high bytes to words). + +Diamo un occhio a come funziona `punpcklbw`. La sintassi per la versione SSE2 è la seguente: + +| PUNPCKLBW xmm1, xmm2/m128 | +| :---- | + +Questo significa che la sua fonte (a destra) può essere un registro xmm o un indirizzo di memoria (m128 significa un indirizzo di memoria con la sintassi `[base + scala*indice + spiazzamento]`) e come destinazione un registro xmm. + +il sito [officedaytime.com](officedaytime.com) ha un buon diagramma per mostrare ciò che sta succedendo + +![cos'è questo](image1.png) + +Puoi vedere come i byte sono alternati dalla parte più bassa di ogni registro rispettivamente. Ma che c'entra con le espansioni? Se il registro src è tutti zero questo alterna i byte in dst con zeri. Questo è conosciuto come una *zero extension* (estensione a zero) in quanto i byte sono senza segno. punpckhbw può essere usato per fare la stessa cosa per i byte alti. + +Ecco un esempio che mostra come funziona questa cosa: + +```assembly +pxor m2, m2 ; azzera m2 + +movu m0, [srcq] +movu m1, m0 ; copia m0 in m1 +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +```m0``` ed ```m1``` ora contengono i byte originali, estesi a zero in parole. Nella prossima lezione, vedrai come istruzioni a tre operandi in AVX rendono il secondo ```movu``` inutile. + +**Estensione del segno** + +I dati con segno sono un rendono le cose un pò più complicate. Per espandere la portata di un intero con segno, dobbiamo usare in processo conosciuto come [Estensione del Segno](https://en.wikipedia.org/wiki/Sign_extension). Questo imbottisce i bit più significativo il bit di segno. Per esempio: -2 in `int8_t` è pari a `0b11111110`. Per effettuare l'estensione del segno e renderlo un `int16_t` il bit più significativo viene ripetuto, per risultare `0b1111111111111110`. + +`pcmpgtb` (packed compare greater than byte) può essere confuso per l'estensione del segno. Facendo il confronto (0 > byte), tutti i bit nella destinazione sono 1 se il byte è negativo, altrimenti i bit alla destinazione vengono tutti impostati a 0. `punpckX` può essere usato come sopra riportato per eseguire l'estensione del segno. Se il byte è negativo il byte corrispondente è `0b11111111` sennò altrimenti è `0x00000000`. Alternare il valore in byte con il risultato di `pcmgtb` esegue un estensione del segno come risultato. + +```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 +``` + +Come puoi vedere c'è un istruzione in più in confronto al caso senza segno. + +**impacchettamento (Packing)** + +`packuswb` (pack unsigned word to byte) e `packsswb` ci permettono di passare da una parola ad un byte. Ci permette di alternare due registri SIMD contenenti due parole in un registro SIMD da un byte. Nota che se i valori eccedono la dimensione del byte, verrano saturati (ovvero limitati al proprio valore massimo). + +**Shuffles (Mescolamenti)** + +Gli *shuffle*, conosciuti anche come permute, sono discutibilmente le istruzioni più importanti nell'elaborazione video e `pshufb` (packed shuffle bytes), disponibile in SSSE3, è la variante più importante. + +Per ogni byte il corrispondente byte sorgente è usato da indice del registro di destinazione, eccetto quando il bit più significativo è zero, il che significa il byte di destinazione è azzerato. È analogo al seguente codice C (anche se in SIMD tutti e 16 le iterazioni del ciclo avvengono in parallelo): + + +```c +for(int i = 0; i < 16; i++) { + if(src[i] & 0x80) + dst[i] = 0; + else + dst[i] = dst[src[i]] +} +``` + +Ecco un semplice esempio in assembly: + +```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 ; mescolatura di m0 basata su m1 +``` + +Da notare che -1 per semplificare la lettura è usato come l'indice di mescolatura per azzerare il byte di output: -1 in byte è pari a 0b11111111 in complemento a due, e quindi il bit più significativo (0x80) è impostato. + +[image1]: From d22f517c6a1ce221fa71a6078bb3d6b574c1cb71 Mon Sep 17 00:00:00 2001 From: Filcent Date: Mon, 11 Aug 2025 17:36:27 +0200 Subject: [PATCH 8/8] Fixed uppercase, simple mistakes --- README.it.md | 2 +- lesson_01/index.it.md | 50 +++++++++++++++++++++---------------------- lesson_02/index.it.md | 40 +++++++++++++++++----------------- lesson_03/index.it.md | 46 +++++++++++++++++++-------------------- 4 files changed, 69 insertions(+), 69 deletions(-) diff --git a/README.it.md b/README.it.md index edd3cdb..b7c1135 100644 --- a/README.it.md +++ b/README.it.md @@ -1,4 +1,4 @@ -Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti faranno aprire gli occhi a cosa sta effeivamente succedendo all'interno del tuo computer. +Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo in uno dei viaggi più interessanti, impegnativi e soddisfacenti nella programmazione. Queste lezioni ti daranno una base nel modo in cui il linguaggio assembly è scritto in FFmpeg e ti apriranno gli occhi a cosa sta effeivamente succedendo all'interno del tuo computer. **Conoscenze Richieste** diff --git a/lesson_01/index.it.md b/lesson_01/index.it.md index f8e0d55..a849fcc 100644 --- a/lesson_01/index.it.md +++ b/lesson_01/index.it.md @@ -11,33 +11,33 @@ Benvenuto alla Scuola del Linguaggio Assembly FFmpeg. Hai fatto il primo passo i **Cos'è il linguaggio assembly?** -Il linguaggio assembly è un linguaggio di programmazione dove il codice che scrivi corrisponde alle istruzioni che una CPU processa. Il linguaggio assembly leggibile dagli umani viene, come suggerito dal nome, *assemblato* in dati binari, conosciuti come *codice macchina*, comprensibile dalla CPU. Potresti vedere il linguaggio assembly riferito come "assemblatore", abbreviato in "asm". +Il linguaggio assembly è un linguaggio di programmazione dove il codice che scrivi corrisponde alle istruzioni che una CPU processa. Il linguaggio assembly leggibile dagli umani viene, come suggerisce il nome, *assemblato* in dati binari, conosciuti come *codice macchina*, comprensibili dalla CPU. Potresti vedere il linguaggio assembly riferito come "assemblatore", abbreviato in "asm". La gran maggioranza del codice di FFmpeg è ciò che è noto come *SIMD, Single Instruction Multiple Data (Istruzione Singola Dati Multipli)*. SIMD è a volte denominata programmazione vettoriale. La maggior parte dei linguaggi di programmazione operano su un elemento di dati alla volta, conosciuto come programmazione scalare. Come avrai potuto intuire, SIMD si presta bene nel processare immagini, video ed audio in quanto hanno molti dati ordinati sequenzialmente in memoria. Ci sono istruzioni specializzate disponibili nella CPU per aiutarci a processare dati in sequenza. -In FFmpeg, vedrai i termini "funzione assembly", "SIMD", e "vettorizzare" usati intercambiabilmente. Fanno tutti riferimento alla stessa cosa: Scrivere una funzione nel linguaggio assembly a mano per processare più elementi di dati in una sola volta. Alcuni progetti potrebbero far riferimento a questi come "nuclei dell' assemblatore". +In FFmpeg, vedrai i termini "funzione assembly", "SIMD", e "vettorizzare" usati intercambiabilmente. Fanno tutti riferimento alla stessa cosa: Scrivere una funzione nel linguaggio assembly a mano per processare più elementi di dati in una sola volta. Alcuni progetti potrebbero far riferimento a questi come "nuclei dell'assemblatore". -Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, degli studenti delle superiori hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effettivamente imparare. +Tutto questo potrebbe sembrare complicato, ma è importante ricordare che in FFmpeg, ci sono stati studenti delle superiori che hanno scritto codice assembly. Come con tutto, imparare è al 50% terminologia, e al 50% effettivamente imparare. **Perchè scriviamo in linguaggio assembly?** Per rendere l'elaborazione multimediale veloce. È molto comune raggiungere un miglioramento di velocità anche di 10 o più volte scrivendo codice assembly, il che è specialmente importante per riprodurre video in tempo reale senza interruzioni. Oltretutto, ciò risparmia energia ed estende l'autonomia delle batterie. È giusto evidenziare che funzioni di codifica e decodifica video sono alcune delle più usate nel mondo, sia da utenti finali che da grandi compagnie e datacenter. Perciò anche un piccolo miglioramento si accumula velocemente. -Online, vedrai spesso che le persone usano *intrinseche*, ovvero funzioni simili al C che mappano ad istruzioni assembly, permettendo uno sviluppo più veloce. In FFmpeg non usiamo intrinseche, ma piuttosto scriviamo codice assembly a mano. Questa rappresenta un area controversa, ma le intrinseche sono solitamente più lente del 10-15% rispetto all'assembly a mano (coloro che supportano le intrinseche non sarebbero d'accordo), in base al compilatore. Per FFmpeg, ogni piccolo miglioramento di velocità aiuta, e per questo scriviamo in codice assembly direttamente. C'è anche chi sostiene che le intrinseche siano difficili da leggere a causa dell'uso della "[Notazione Ungara](https://it.wikipedia.org/wiki/Notazione_ungara)" +Online, vedrai spesso che le persone usano *intrinseche*, ovvero funzioni simili al C che vengono mappate ad istruzioni assembly, permettendo uno sviluppo più veloce. In FFmpeg non usiamo intrinseche, ma piuttosto scriviamo codice assembly a mano. Questa rappresenta un area controversa, ma le intrinseche sono solitamente più lente del 10-15% rispetto all'assembly scritto a mano (coloro che supportano le intrinseche non sarebbero d'accordo), in base al compilatore. Per FFmpeg, ogni piccolo miglioramento di velocità aiuta, e per questo scriviamo in codice assembly direttamente. C'è anche chi sostiene che le intrinseche siano difficili da leggere a causa dell'uso della "[Notazione Ungara](https://it.wikipedia.org/wiki/Notazione_ungara)" Potresti anche vedere *assembly in linea* (ovvero senza usare intrinseche) da qualche parte in FFmpeg per ragioni storiche, o in progetti come il Kernel Linux a causa di casi d'uso molto specifici. Questo è quando il codice assembly non è in un file separato, ma scritto in linea con codice C. L'opinione prevalente in progetti come FFmpeg è che questo codice è difficile da leggere, non ampiamente supportato da compilatori e non manutenibile. -Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che nulla di tutto ciò è necessario e che il compilatore può fare tutta questa "vettorizzazione" al posto tuo. Almeno per lo scopo di imparare, ignorali. test recenti, ad esempio nel [the dav1d project](https://www.videolan.org/projects/dav1d.html) mostrano una velocizzazione circa di 2 volte grazie a questa vettorizzazione automatica, mentre le versioni scritte a mano riuscivano a raggiungere le 8 volte. +Infine, in rete vedrai molti esperti auto-proclamati tali che affermano che nulla di tutto ciò è necessario e che il compilatore può fare tutta questa "vettorizzazione" al posto tuo. Almeno per lo scopo di imparare, ignorali. Test recenti, ad esempio nel [the dav1d project](https://www.videolan.org/projects/dav1d.html) mostrano una velocizzazione circa di 2 volte grazie a questa vettorizzazione automatica, mentre le versioni scritte a mano riuscivano a raggiungere le 8 volte. **Gusti del linguaggio assembly** Queste lezioni si concentreranno sull'assembly x86 a 64 bit. Questo è anche conosciuto come amd64, anche se funziona lo stesso su CPU Intel. Ci sono altri tipi di assembly per altre CPU, come ARM e RISC-V e potenzialmente nel futuro queste lezioni saranno estese per coprire tali. -Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo la sintassi Intel. +Ci sono due gusti di sintassi di assembly x86 che vedrai in rete: AT&T ed Intel. La sintassi AT&T è più complicata rispetto alla sintassi Intel, quindi useremo quest'ultima. **Materiali di supporto** -Potresti sorprenderti al fatto che libri o risorse in rete come Stack Overflow non sono particolarmente utili come riferimenti. Questo è in parte a causa della nostra scelta di scrivere codice assembly a mano con sintassi Intel. Ma è anche perchè molte risorse in rete si concentrano alla programmazione di sistemi operativi o alla programmazione hardware, solitamente usando codice non-SIMD. L'assembly FFmpeg è particolarmente concentrato alla processazione di immagini ad alte prestazioni, e come vedrai è un approccio particolarmente unico alla programmazione in assembly. Detto ciò, sarà facile comprendere altri casi d'uso dell'assembly una volta che avrai completato queste lezioni. +Potresti sorprenderti al fatto che libri o risorse in rete come Stack Overflow non sono particolarmente utili come riferimenti. Questo è in parte a causa della nostra scelta di scrivere codice assembly a mano con sintassi Intel. Ma è anche perchè molte risorse in rete si concentrano alla programmazione di sistemi operativi o alla programmazione hardware, solitamente usando codice non-SIMD. L'assembly FFmpeg è particolarmente concentrato all'elaborazione di immagini ad alte prestazioni, e come vedrai è un approccio particolarmente unico alla programmazione in assembly. Detto ciò, sarà facile comprendere altri casi d'uso di questo linguaggio una volta che avrai completato queste lezioni. Molti libri vanno molto sui dettagli dell'architettura del computer prima di insegnare l'assembly. Questo va bene se vuoi imparare quello, ma dal nostro punto di vista, è come studiare come funziona un motore prima di imparare a guidare. @@ -47,12 +47,12 @@ Un server discord è disponibile per rispondere a domande: [https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) **Registri** -I registri sono aree nella CPU dove i dati possono essere processati. Le CPU non eseguiranno operazioni direttamente sulla memoria, ma invece i dati verrano prima caricati nei registri, processati e poi scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. +I registri sono aree nella CPU dove i dati possono essere processati. Le CPU non eseguiranno operazioni direttamente sulla memoria, ma invece i dati verranno prima caricati nei registri, processati e poi scritti nuovamente in memoria. Nel linguaggio assembly, in generale, non è possibile copiare dati da un luogo in memoria ad un altro senza prima far passare quei dati attraverso un registro. **Registri a Scopo Generale** -Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono contenere dati, in questo caso un valore a 64 bit, o un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. +Il primo tipo di registro è conosciuto come Registro a Scopo Generale, in inglese *General Purpose Register* (GPR). I GPR sono riferiti come a scopo generale in quanto possono contenere sia dati, in questo caso un valore a 64 bit, che un indirizzo di memoria (un puntatore). Un valore in un GPR può essere processato attraverso operazioni come addizione, moltiplicazione, spostamento, ecc. -nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scritto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e vengono astratte. +Nella maggior parte dei libri di assembly, ci sono capitoli dedicati alle sottigliezze dei GPR, il loro contesto storico ecc. Questo è perchè i GPR sono importanti quando viene alla programmazione di sistemi operativi, all'ingegneria inversa, ecc. Nel codice assembly scritto in FFmpeg, i GPR fanno più da impalcatura che altro e la maggior parte delle volte le loro complessità non sono necessarie e quindi vengono astratte. **Registri vettoriali** I registri vettoriali (SIMD), come suggerisce il nome, contengono più elementi di dati. Ci sono vari tipi di registri vettoriali: @@ -96,7 +96,7 @@ I caratteri in grassetto saranno importanti dopo. **x86inc.asm include** Vedrai come in molti esempi includeremo il file x86inc.asm. x86inc.asm è un livello d'astrazione leggero usato in FFmpeg, x264 e dav1d per rendere la vita di un programmatore d'assembly più semplice. Aiuta in molti modi, ma tanto per iniziare, una delle cose che fa è etichettare i GPR, r0, r1, r2. Questo significa che non dovrai ricordarti i nomi dei registri. Come menzionato prima, i GPR sono generalmente solo impalcature quindi questo ci semplifica la vita. -**Un semplice estratto du asm scalare** +**Un semplice estratto di asm scalare** Diamo un occhio a questo estratto semplice (e molto artificiale) di asm scalare (codice assembly che opera su elementi di dati individuali, uno alla volta, all'interno di ogni Istruzione) per vedere che sta succedendo: @@ -107,9 +107,9 @@ dec r0q imul r0q, 5 ``` -nella prima linea, il *valore immediato* 3 (un valore memorizzato direttamente nel codice assembly stesso, opposto ad un valore recuperato dalla memoria) è memorizzato nel registro r0 come parola quadrupla. Da notare che nella sintassi Intel, l'operando di origine (il valore o il luogo che fornisce i dati, trovatosi alla destra) è trasferito all'operando di destinazione (il luogo ricevente i dati, trovatosi alla sinistra), simile al comportamento di memcpy. Può anche assere letto come "r0q = 3", dato che l'ordine è lo stesso. il suffisso di r0 "q" designa il registro ad essere usato come parola quadrupla. inc incrementa il valore facendo in modo che r0q contenga 4, dec decrementa il valore nuovamente a 3. imul moltiplica il valore per 5. Quindi alla fine, r0q contiene 15. +Nella prima linea, il *valore immediato* 3 (un valore memorizzato direttamente nel codice assembly stesso, opposto ad un valore recuperato dalla memoria) è memorizzato nel registro `r0` come parola quadrupla. Da notare che nella sintassi Intel, l'operando di origine (il valore o il luogo che fornisce i dati, trovatosi alla destra) è trasferito all'operando di destinazione (il luogo ricevente i dati, trovatosi alla sinistra), simile al comportamento di memcpy. Può anche assere letto come "`r0q = 3`", dato che l'ordine è lo stesso. Il suffisso di `r0` "`q`" designa che il registro venga usato come parola quadrupla. `inc` incrementa il valore facendo in modo che `r0q` contenga 4, `dec` decrementa il valore nuovamente a 3. `imul` moltiplica il valore per 5. Quindi alla fine, `r0q` contiene 15. -È da notare che le istruzioni leggibili dagli umani come mov e inc, assemblate poi in codice macchina dall'assembler, sono chiamate *mnemoniche*. Potresti vedere in rete e su libri mnemoniche rappresentate in maiuscolo con lettere come MOV e INC ma queste sono le stesse delle loro versioni in minuscolo. In FFmpeg, utilizziamo mnemoniche in minuscolo e teniamo il maiuscolo riservato per le macro. +È da notare che le istruzioni leggibili dagli umani come `mov` e `inc`, assemblate poi in codice macchina dall'assembler, sono chiamate *mnemoniche*. Potresti vedere in rete e su libri mnemoniche rappresentate in maiuscolo, come `MOV` e `INC` ma queste sono identiche alla loro versione in minuscolo. In FFmpeg, utilizziamo mnemoniche in minuscolo e riserviamo il maiuscolo alle macro. **Comprendere una funzione vettoriale di base** @@ -133,57 +133,57 @@ cglobal add_values, 2, 2, 2, src, src2 RET ``` -Attraversiamo il codice linea per linea: +Analizziamo il codice riga per riga: ```assembly %include "x86inc.asm" ``` -Questa è una "intestazione" sviluppata nelle comunità di x264, FFmpeg e dav1d per dare aiutanti, nomi e macro predefiniti (come cglobal sotto) per semplificare la scrittura dell'assembly. +Questa è una "intestazione" sviluppata nelle comunità di x264, FFmpeg e dav1d per dare aiutanti, nomi e macro predefiniti (come `cglobal` più sotto) per semplificare la scrittura dell'assembly. ```assembly SECTION .text ``` -Questo denota la sezione dove è piazzato il codice che si vuole eseguire. Questo è in contrasto alla sezione .data, dove possono essere inseriti i dati costanti. +Questa riga denota la sezione dove è piazzato il codice che si vuole eseguire. Questo è in contrasto alla sezione `.data`, dove possono essere inseriti invece i dati costanti. ```assembly ;static void add_values(uint8_t *src, const uint8_t *src2) INIT_XMM sse2 ``` -La prima riga è un commento (il punto e virgola ";" in asm è come "//" in C) che ci mostra come l'argomento della funzione appare in C. La seconda riga ci mostra come stiamo inizializzando la funzione per usare i registri XMM, usando il set di istruzioni sse2. Questo perchè paddb è un istruzione sse2. Copriremo l'sse2 in ulteriori dettagli nella prossima lezione. +La prima riga è un commento (il punto e virgola `;` in asm è come `//` in C) che ci mostra come l'argomento della funzione appare in C. La seconda riga ci mostra come stiamo inizializzando la funzione per usare i registri XMM, usando il set di istruzioni sse2. Questo perchè `paddb` è un istruzione sse2. Copriremo l'sse2 in ulteriori dettagli nella prossima lezione. ```assembly cglobal add_values, 2, 2, 2, src, src2 ``` -Questa è una riga importante in quanto definisce una funzione C di nome "add_values". +Questa è una riga importante in quanto definisce una funzione C di nome `add_values`. Attraversiamo ogni elemento uno per volta: -* Il parametro dopo mostra cbe ha due argomenti della funzione. +* Il parametro dopo mostra `cbe` ha due argomenti della funzione. * Il parametro successivo mostra che useremo due GPR come argomenti. In alcuni casi potremo voler usare più GPR, quindi dovremo dire a x86util che ce ne servono di più. * Il parametro successivo dice a x86util quanti registri XMM useremo. * I due parametri dopo sono le etichette per gli argomenti della funzione. -Vale la pena notare che codice più vecchio potrebbe non avere etichette per gli argomenti della funzione ma invece potrebbe riferirsi ai GPR usando direttamente r0, r1 ecc. +Vale la pena notare che codice più vecchio potrebbe non avere etichette per gli argomenti della funzione ma potrebbe invece riferirsi ai GPR usando direttamente r0, r1 ecc. ```assembly movu m0, [srcq] movu m1, [src2q] ``` -movu è l'abbreviazione di movdqw (move double quad unaligned, sposta doppia quadrupla non allineata). Parleremo dell'allineamento in un'altra lezione, ma per ore movu può essere trattato come uno spostamento a 128 bit da [srcq]. Nel caso di mov, le parentesi quadre indicano che l'indirizzo in [srcq] sta venendo dereferenziato, l'equivalente di **src in C.* Questo è conosciuto come un caricamento (load). Da notare che il suffisso "q" si riferisce alla dimensione del puntatore *(*ovvero in C rappresenta *sizeof(*src) == 8 su sistemi a 64 bit, e x86asm è abbastanza intelligente da usare 32 bit su sistemi a 32 bit) ma l'operazione di caricamento sottostante rimane a 128 bit. +`movu` è l'abbreviazione di `movdqw` (move double quad unaligned, sposta doppia quadrupla non allineata). Parleremo dell'allineamento in un'altra lezione, ma per ore `movu` può essere trattato come uno spostamento a 128 bit da `[srcq]`. Nel caso di `mov`, le parentesi quadre indicano che l'indirizzo in `[srcq]` sta venendo dereferenziato, l'equivalente di \*src in C. Questo è conosciuto come un caricamento (*load*). Da notare che il suffisso "`q`" si riferisce alla dimensione del puntatore (ovvero in C l'equivalente di \*sizeof(\*src) == 8 su sistemi a 64 bit, e x86asm è abbastanza intelligente da usare 32 bit su sistemi a 32 bit) ma l'operazione di caricamento sottostante rimane a 128 bit. -È da notare come non ci riferiamo ai registri vettoriali con il loro nome intero, in questo caso xmm0, ma come m0, una forma astratta. Nelle lezioni future vedremo come ciò significa sia possibile scrivere codice una volta sola e fare in modo funzioni su varie dimensioni di registri SIMD. +È da notare come non ci riferiamo ai registri vettoriali con il loro nome intero, in questo caso xmm0, ma `m0`, una forma astratta. Nelle lezioni future vedremo come ciò significa sia possibile scrivere codice una volta sola e fare in modo funzioni su varie dimensioni di registri SIMD. ```assembly paddb m0, m1 ``` -paddb (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni registro come mostrato qui sotto. Il prefisso "P" sta per "Packed" (Impacchettato) ed è usato per identificare istruzioni vettoriali vs istruzioni scalari. +`paddb` (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni registro come mostrato qui sotto. Il prefisso "P" sta per "Packed" (Impacchettato) ed è usato per identificare istruzioni vettoriali vs istruzioni scalari. @@ -204,7 +204,7 @@ paddb (leggilo come *p-add-b*, p aggiunge b) sta aggiungendo ogni byte ad ogni r movu [srcq], m0 ``` -Questo è noto come un immagazzinaggio (*store*). I dati sono scritti all'indirizzo nel puntatore srcq. +Questa è nota come una memorizzazione (*store*). I dati sono scritti all'indirizzo nel puntatore `srcq`. ```assembly RET @@ -212,6 +212,6 @@ RET Questa è una macro per denotare il ritorno della funzione. Praticamente tutte le funzioni in FFmpeg modificano i dati negli argomenti invece che far ritornare un valore. -Come vedrai nel compito, creeremo puntatori di funzione a funzioni assembly e useremo questi dove possiamo +Come vedrai nel compito, creeremo puntatori di funzione a funzioni assembly e li useremo dove possibile. [Prossima Lezione](../lesson_02/index.it.md) diff --git a/lesson_02/index.it.md b/lesson_02/index.it.md index 646eeb7..a5bbc04 100644 --- a/lesson_02/index.it.md +++ b/lesson_02/index.it.md @@ -1,8 +1,8 @@ **Lezione Linguaggio Assembly FFmpeg Due** -Ora che hai scritto la tua prima funzione in linguaggio assembly, introdurremo rami e cicli. +Ora che hai scritto la tua prima funzione in linguaggio assembly, introdurremo ramificazioni e cicli. -Dobbiamo prima introdurre l'idea di etichette e salti. Nell'esempio artificiale qui sotto, l'istruzione jmp sposta l'istruzione del codice dopo ".loop:". ".loop:" è conosciuta come un'*etichetta*, il fatto che sia prefissata dal punto indica che è un *etichetta locale*, il che effettivamente ti permette di riusare lo stesso nome dell'etichetta attraverso varie funzioni. Questo esempio, ovviamente, mostra un ciclo infinito, ma estenderemo quest'ultimo più tardi a qualcosa di più realistico. +Dobbiamo prima però introdurre le idee di etichette e salti. Nell'esempio artificiale qui sotto, l'istruzione `jmp` sposta l'istruzione del codice dopo "`.loop:`". "`.loop:`" è conosciuta come un'*etichetta*, il fatto che sia prefissata dal punto indica che è un *etichetta locale*, il che effettivamente ti permette di riusare lo stesso nome dell'etichetta attraverso varie funzioni. Questo esempio, ovviamente, mostra un ciclo infinito, ma estenderemo quest'ultimo più tardi a qualcosa di più realistico. ```assembly mov r0q, 3 @@ -11,9 +11,9 @@ mov r0q, 3 jmp .loop ``` -Prima di creare un ciclo realistico dobbiamo introdurre il registro *FLAGS* (Bandiere). Non ci soffermeremo troppo sulle complessità di *FLAGS*, (in quanto, di nuovo, le operazioni sui GPR fanno per la maggior parte da impalcatura) ma ci sono varie flags come la Zero-flag, la Sign-flag e la Overflow-flag che sono alzate o abbassate in base al risultato della maggior parte delle operazioni non-mov su dati scalari come operazioni aritmetiche e spostamenti. +Prima di creare un ciclo realistico dobbiamo introdurre il registro *FLAGS* (Bandiere). Non ci soffermeremo troppo sulle complessità di *FLAGS*, (in quanto, di nuovo, le operazioni sui GPR fanno per la maggior parte da impalcatura) ma ci sono varie flags come la Zero-flag, la Sign-flag e la Overflow-flag che sono alzate o abbassate in base al risultato della maggior parte delle operazioni non-`mov` su dati scalari come operazioni aritmetiche e spostamenti. -Ecco un esempio dove il contatore del ciclo conta alla rovescia fino allo zero e jg (*jump if greater than zero*, salta se più di zero) è la condizione del ciclo. dec r0q imposta il valore delle FLAGs in base al valore di r0q dopo l'istruzione, e puoi quindi saltare in base a loro. +Ecco un esempio dove il contatore del ciclo conta alla rovescia fino allo zero e `jg` (*jump if greater than zero*, salta se più di zero) è la condizione del ciclo. `dec r0q` imposta il valore delle FLAGs in base al valore di `r0q` dopo l'istruzione, e puoi quindi saltare in base ad esse. ```assembly mov r0q, 3 @@ -54,9 +54,9 @@ xor r0q, r0q jl .loop ; salta se (r0q - 3) < 0, ovvero (r0q < 3) ``` -Ci sono alcune cose da notare in questo frammento di codice. Prima tra tutte è ```xor r0q, r0q``` ovvero un modo usuale per impostare un registro a zero, il chè in alcuni sistemi è più veloce di ```mov r0q, 0```, in quanto, semplicemente, non si sta effettivamente caricando nulla. Può anche essere usato su registri SIMD attraverso ```pxor m0, m0``` per azzerare un intero registro. La prossima cosa da notare è l'uso di cmp. Praticamente, cpm sottrae il secondo registro dal primo (senza memorizzare il risultato da nessuna parte) e imposta *FLAGS*, ma come scritto nel commento, può essere letto insieme al salto, (jl = *jump if less than zero*, salta se meno di zero) per saltare se ```r0q < 3```. +Ci sono alcune cose da notare in questo frammento di codice. Prima tra tutte è ```xor r0q, r0q``` ovvero un modo diffuso per impostare un registro a zero, più veloce di ```mov r0q, 0``` in alcuni sistemi, in quanto, semplicemente, non si sta effettivamente caricando nulla. Può anche essere usato su registri SIMD attraverso ```pxor m0, m0``` per azzerare un intero registro. La prossima cosa da notare è l'uso di `cmp`. Praticamente, `cpm` sottrae il secondo registro dal primo (senza memorizzare il risultato da nessuna parte) e imposta *FLAGS*, ma come scritto nel commento, può essere letto insieme al salto, (`jl` = *jump if less than zero*, salta se meno di zero) per saltare se ```r0q < 3```. -Da notare come c'è un istruzione in più in questo frammento. Generalmente, meno istruzioni significano codice più veloce, il che è il motivo per cui il frammento precedente è preferibile a questo. Come vedrai nelle lezioni future, ci sono più trucchi per evitare di scrivere istruzioni extra e avere *FLAGS* impostato da aritmetica o da altre operazioni. Da notare anche come non stiamo scrivendo assembly per corrispondere esattamente a cicli in C, ma stiamo scrivendo i cicli facendo in modo che siano il più veloce possibile in assembly. +Da notare come c'è un istruzione in più in questo frammento di codice. Generalmente, meno istruzioni significano codice più veloce, il che è il motivo per cui l'esempio precedente è preferibile a questo. Come vedrai nelle lezioni future, ci sono più trucchi per evitare di scrivere istruzioni extra e avere il regitro *FLAGS* impostato da aritmetica o da altre operazioni. Da notare anche come non stiamo scrivendo assembly per corrispondere esattamente a cicli in C, ma stiamo scrivendo cicli facendo in modo che siano il più veloce possibile in assembly. A seguito alcune mnemoniche per i salti che andrai ad usare (ci sono anche *FLAGS* per completezza, ma non devi saperne le specifiche per scrivere cicli): @@ -71,7 +71,7 @@ A seguito alcune mnemoniche per i salti che andrai ad usare (ci sono anche *FLAG **Costanti** -Vediamo degli esempi che ci mostrano come usare le costanti: +Vediamo alcuni esempi che ci mostrano come usare le costanti: ```assembly SECTION_RODATA @@ -86,11 +86,11 @@ constants_2: times 2 dw 4,3,2,1 Questa etichette, convertite poi in un indirizzo di memoria dall'assembler, possono essere usate in caricamenti (ma non scritture, in quanto sono a sola lettura). Alcune istruzioni prendono come operando un indirizzo di memoria quindi possono essere usate senza caricamenti espliciti in un registro (ci sono pro e contro nel fare questo). -**Offsets** (Sfalsamenti) +**Offsets (Sfalsamenti)** -Gli sfalsamenti sono la distanza (in byte) tra elementi consecutivi in memoria Lo sfalsamento è determinato dalla **dimensione di ogni elemento** nella struttura di dati. +Gli sfalsamenti sono la distanza (in byte) tra elementi consecutivi in memoria. Lo sfalsamento è determinato dalla **dimensione di ogni elemento** nella struttura di dati. -Ora che sei in grado di scrivere cicli, è arrivata l'ora di andare a recuperare dati. Ci sono però alcune differenze rispetto al C. Osserviamo il seguente ciclo in C: +Ora che sei in grado di scrivere cicli, è giunta l'ora di andare a recuperare dati. Ci sono però alcune differenze rispetto al C. Osserviamo il seguente ciclo in C: ```c uint32_t data[3]; @@ -100,20 +100,20 @@ for(i = 0; i < 3; i++) { } ``` -Lo sfalsamento di 4 byte tra gli elementi di data è precalcolato dal compilatore C. Quando però si scrive assembly a mano, bisogna anche calcolare lo sfalsamento a mano. +Lo sfalsamento di 4 byte tra gli elementi di data è pre-calcolato dal compilatore C. Quando però si scrive assembly a mano, bisogna anche calcolare lo sfalsamento a mano. -Guardiamo alla sintassi per il calcolo dell'indirizzo di memoria. Questo si applica a tutti i tipi di indirizzi di memoria. +Osserviamo la sintassi per il calcolo dell'indirizzo di memoria. Questo si applica a tutti i tipi di indirizzi di memoria. ```assembly [base + scala*indice + spiazzamento] ``` * base - Questo è un GPR (solitamente un puntatore da un argomento di una funzione C) -* scala - Questa può essere 1, 2, 4, 8. 1 è il predefinito +* scala - Questa può essere 1, 2, 4, 8. 1 è predefinito * indice - Questo è un GPR (solitamente un contatore di ciclo) -* spiazzamento - Questo è un numero intero (fino a 32 bit). Lo spiazzamento è uno sfalsamento all'intero dei dati +* spiazzamento - Questo è un numero intero (fino a 32 bit). Lo spiazzamento è uno sfalsamento all'interno dei dati -x86asm provvede la costante mmsize, la quale ti fa sapere la dimensione del registro SIMD con cui stai lavorando. +x86asm provvede la costante `mmsize`, la quale ti permette di sapere la dimensione del registro SIMD con cui stai lavorando. Ecco un esempio semplice (e insensato) per illustrare il caricamento da sfalsamenti personalizzati: @@ -135,23 +135,23 @@ jg .loop RET ``` -Da notare come su ```movu m1, [srcq+2*r1q+3+mmsize]``` l'assembler pre-calcolerà lo spiazzamento costante corretto da usare. Nella prossima lezione ti mostreremo un trucchetto per evitare il fare un add e un dec nel ciclo, rimpiazzandoli con un singolo add. +Da notare come su ```movu m1, [srcq+2*r1q+3+mmsize]``` l'assembler pre-calcolerà lo spiazzamento costante corretto da usare. Nella prossima lezione ti mostreremo un trucchetto per evitare il fare un `add` e un `dec` nel ciclo, rimpiazzandoli con un singolo `add`. **LEA** -Ora che comprendi gli sfalsamenti sarai in grado di usare lea (*Load Effective Address*, Carica Indirizzo Effettivo). Questo ti permette di eseguire moltiplicazione e addizione in una sola istruzione, che sarà più veloce rispetto all'uso di più istruzioni. Ci sono, ovviamente, limitazioni per cosa puoi effettivamente moltiplicare ed aggiungere ma ciò non influisce sul fatto che lea sia un istruzione molto potente. +Ora che comprendi gli sfalsamenti, sarai in grado di usare `lea` (*Load Effective Address*, Carica Indirizzo Effettivo). Questo ti permette di eseguire moltiplicazione e addizione in una sola istruzione, che sarà quindi più veloce rispetto all'uso di più istruzioni. Ci sono, ovviamente, limitazioni su cosa puoi effettivamente moltiplicare ed aggiungere ma ciò non influisce sul fatto che `lea` sia un istruzione molto potente. ```assembly lea r0q, [base + scale*index + disp] ``` -Contrario al suo nome, LEA può essere usato per normale aritmetica e calcolo d'indirizzo. Puoi fare qualcosa tanto complicato quanto: +Contrario al suo nome, `lea` può essere usata per normale aritmetica e calcolo d'indirizzo. Puoi fare qualcosa tanto complicato quanto: ```assembly lea r0q, [r1q + 8*r2q + 5] ``` -È da notare come questo non abbia effetto sui contenuti di r1q e r2q. Non ha effetto nemmeno su *FLAGS* (quindi non puoi saltare in base al risultato dell'operazione). Usando LEA si evitano tutte queste istruzioni e registri tempranei (questo codice non è equivalente in quanto add cambia *FLAGS*) +È da notare come questo non abbia effetto sui contenuti di `r1q` e `r2q`. Non ha effetto nemmeno su *FLAGS* (quindi non puoi saltare in base al risultato dell'operazione). Usando `lea` si evitano tutte queste istruzioni e registri temporanei (questo codice non è equivalente in quanto `add` cambia *FLAGS*): ```assembly movq r0q, r1q @@ -161,7 +161,7 @@ add r3q, 5 add r0q, r3q ``` -Vedrai come lea è molto usato per impostare indirizzi prima di cicli o per eseguire calcoli come qui sopra. Ovviamente è da notare che non è possibile eseguire ogni tipo di moltiplicazione e addizione, ma moltiplicazioni per 1, 2, 4, 8 e addizioni di uno sfalsamento fisso sono comuni. +Vedrai come `lea` è molto usata per impostare indirizzi prima di cicli o per eseguire calcoli come qui sopra. Ovviamente è da notare che non è possibile eseguire ogni tipo di moltiplicazione e addizione, ma moltiplicazioni per 1, 2, 4, 8 e addizioni per uno sfalsamento fisso sono comuni. Nel compito dovrai caricare una costante ed addizionare i valori ad un vettore SIMD in un ciclo diff --git a/lesson_03/index.it.md b/lesson_03/index.it.md index e2cbfb3..7375dfb 100644 --- a/lesson_03/index.it.md +++ b/lesson_03/index.it.md @@ -4,13 +4,13 @@ Ora spiegheremo un po' di terminologia tecnica e vi daremo una piccola lezione d **Set di Istruzioni** -Come avrei visto nella lezione precedente, abbiamo parlato di SSE2, ovvero un set di istruzioni SIMD. Quando una nuova generazione di CPU viene rilasciata, potrebbe avere nuove istruzioni e a volte una maggiore dimensione dei registri. La storia del set di istruzioni x86 è molto complessa, quindi questa qui sotto è una storia semplificata (ci sono molte più sottocategorie) +Come avrei visto nella lezione precedente, abbiamo parlato di SSE2, ovvero un set di istruzioni SIMD. Quando una nuova generazione di CPU viene rilasciata, potrebbe avere nuove istruzioni e a volte una maggiore dimensione dei registri. La storia del set di istruzioni x86 è molto complessa, quindi questa qui sotto è una storia semplificata (ci sarebbero sennò molte più sottocategorie) * MMX - Rilasciato nel 1997, primi SIMD nei processori Intel, registri a 64 bit, storico * SSE (Streaming SIMD Extensions) - Rilasciato nel 1999, registri a 128 bit * SSE2 - Rilasciato nel 2000, molte nuove istruzioni * SSE3 - Rilasciato nel 2004, prime istruzioni orizzontali -* SSSE3 (Supplemental SSE3) - Rilasciato nel 2006, nuove istruzioni ma più importante `pshubfb` +* SSSE3 (Supplemental SSE3) - Rilasciato nel 2006, nuove istruzioni ma soprattutto `pshubfb` * SSE4 - Rilasciato nel 2008, varie nuove istruzioni tra cui minimo e massimo impacchettato * AVX - Rilasciato nel 2011, registri a 256 bit (solo `float`) a nuova sintassi a tre operandi * AVX2 - Rilasciato nel 2013, registri a 256 bit per istruzioni con numeri interi @@ -18,13 +18,13 @@ Come avrei visto nella lezione precedente, abbiamo parlato di SSE2, ovvero un se * AVX512ICL - Rilasciata nel 2019, rimuove la limitazione della frequenza. * AVX10 - In arrivo -È giusto notare che i set di istruzioni possono essere sia rimossi che aggiunti alle CPU. Ad esempio, AVX512 fu [rimosso](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/)), polemicamente, nella 12esima generazione di CPU Intel. Questo è il motivo per cui FFmpeg rileva la CPU su cui sta eseguendo e le capacità di essa all'esecuzione. +È giusto notare che i set di istruzioni possono essere sia rimossi che aggiunti alle CPU. Ad esempio, AVX512 fu [rimosso](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/)), in modo controverso, nella 12esima generazione di CPU Intel. Questo è il motivo per cui FFmpeg rileva la CPU e le capacità di essa all'esecuzione. -Come avrai potuto osservare nel compito, i puntatori di funzione sono di default in C e sono rimpiazzati con una particolare variante di set di istruzioni. Questo significa che una volta eseguita la rilevazione non servirà più eseguirla. Ciò è in contrasto a ciò che molte applicazioni proprietarie, ovvero codificare un set di istruzioni particolare, rendendo un computer perfettamente operazionale obsoleto. Questo permette anche di usare/non usare funzioni ottimizzate all'esecuzione. Uno dei grandi benefici dell'open source è proprio questo. +Come avrai potuto osservare nel compito, i puntatori di funzione sono di default in C e sono rimpiazzati con una particolare variante di set di istruzioni. Questo significa che una volta eseguita la rilevazione non servirà più ri-eseguirla. Questo è in contrasto a ciò che molte applicazioni proprietarie fanno, ovvero essere scritte per un set di istruzioni particolare, rendendo un computer perfettamente operazionale obsoleto. Questo permette anche di usare/non usare funzioni ottimizzate all'esecuzione. Uno dei grandi benefici dell'open source è proprio questo. -Programmi come FFmpeg vengono usati su miliardi di dispositivi in tutto il mondo, e alcuni di questi potrebbero essere molto vecchi. Tecnicamente, FFmpeg supporta anche macchine che sopportano soltanto SSE, anche se ormai sono al 25esimo compleanno! Fortunatamente x86inc.asm è in grado di comunicarti se stai usando un istruzione che non è disponibile in un particolare set di istruzioni. +Programmi come FFmpeg vengono usati su miliardi di dispositivi in tutto il mondo, e alcuni di questi potrebbero essere molto vecchi. Tecnicamente, FFmpeg supporta anche macchine che supportano soltanto SSE, anche se sono ormai al loro 25esimo compleanno! Fortunatamente x86inc.asm è in grado di comunicarti se stai usando un istruzione che non è disponibile in un particolare set di istruzioni. -Per dare un idea delle capacità effettive al giorno d'oggi, ecco la disponibilità di set di istruzioni dallo [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) del novembre 2024: +Per dare un idea delle capacità effettive delle machcine al giorno d'oggi, ecco la disponibilità di set di istruzioni dallo [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) del novembre 2024 (ovviamente questo sondaggio è sbilanciato dalla parte dei gamer): | Set di Istruzioni | disponibilità | | :---- | :---- | @@ -36,11 +36,11 @@ Per dare un idea delle capacità effettive al giorno d'oggi, ecco la disponibili | AVX2 | 94.44% | | AVX512 (Steam does not separate between AVX512 and AVX512ICL) | 14.09% | -Per un applicazione come FFmpeg con miliardi di utenti, anche lo 0.1% è un gran numero di utenti e bug report se qualcosa va storto. FFmpeg ha un infrastruttura di test molto estensiva per testare le differenze di CPU/SO/Compilatore nella nostra [Suite di test FATE](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Ogni singolo commit è eseguito su centinaia di macchine per essere sicuri nulla si rompa. +Per un applicazione come FFmpeg con miliardi di utenti, anche lo 0.1% è un gran numero di utenti e bug report se qualcosa non funziona. FFmpeg ha un infrastruttura di test molto estensiva per testare le differenze di CPU/SO/Compilatore nella nostra [Suite di test FATE](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Ogni singolo commit è eseguito su centinaia di macchine per essere sicuri nulla vada storto. Intel ha un manuale dei set di istruzioni dettagliato qui: [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) -può essere abbastanza scomodo cercare attraverso un PDF, quindi qui puoi trovare un alternativa web non ufficiale: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/) +Può essere abbastanza scomodo cercare attraverso un PDF, quindi qui puoi trovare un alternativa web non ufficiale: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/) È anche dispoibile una rappresentazione visuale delle istruzioni SIMD qui: [https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/) @@ -75,7 +75,7 @@ cglobal add_values, 3, 3, 2, src, src2, width RET ``` -Attraversiamola un passo alla volta, in quanto potrebbe fare confusione: +Attraversiamola un passo alla volta, in quanto potrebbe causare confusione: ```assembly add srcq, widthq @@ -90,28 +90,28 @@ La larghezza è aggiunta ad ogni puntatore in modo che ogni puntatore ore punti movu m1, [src2q+widthq] ``` -I caricamenti sono eseguiti con `vidthq` negativa. Quindi alla prima iterazione `[srcq.widthq]` punta all'indirizzo di `srcq` originale, ovvero l'inizio del buffer. +I caricamenti sono eseguiti con `widthq` negativa. Quindi alla prima iterazione `[srcq.widthq]` punta all'indirizzo di `srcq` originale, ovvero l'inizio del buffer. ```assembly add widthq, mmsize jl .loop ``` -`mmsize` è aggiunto a `widthq` negativo portandolo più vicino allo zero. La condizione del ciclo ora è `jl` (salta se meno di zero). Questo trucco fa in modo che `widthq` è usato **sia** come offset del puntatore **che** come contatore, risparmiando un'istruzione `cmp`. Questo oltretutto permette sia di usare dello sfalsamento del puntatore in più caricamenti e memorizzazioni che di usare multipli di quel puntatore se necessario (ricordatelo per questo compito). +`mmsize` è aggiunto a `widthq` negativo portandolo più vicino allo zero. La condizione del ciclo ora è `jl` (salta se meno di zero). Questo trucco fa in modo che `widthq` è usato **sia** come offset del puntatore **che** come contatore, risparmiando un'istruzione `cmp`. Questo oltretutto permette di usare lo sfalsamento del puntatore in più caricamenti e memorizzazioni e di usare multipli di quel puntatore se necessario (ricordatelo per questo compito). **Allineamento** -In tutti i nostri esempi abbiamo usato `movu` per evitare di avere a che fare con la questione dell'allineamento. Molte CPU possono caricare e memorizzare dati più velocemente se quei dati sono allineati, ovvero se l'indirizzo di memoria è divisibile per la dimensione del registro SIMD. Dove possibile, è buono provare ad usare carichi e memorizzazioni allineate in FFmpeg usando `mova`. +In tutti i nostri esempi abbiamo usato `movu` per evitare di avere a che fare con la questione dell'allineamento. Molte CPU possono caricare e memorizzare dati più velocemente se quei dati sono allineati, ovvero se l'indirizzo di memoria è divisibile per la dimensione del registro SIMD. Dove possibile, è buono provare ad usare caricamenti e memorizzazioni allineate in FFmpeg usando `mova`. -In FFmpeg, `av_malloc` è in grado di provvedere memoria allineata nell'*heap* e la direttiva preprocessore DECLARE_ALIGNED C ci può dare memoria allineata nello *stack*. Se `mova` è utilizzato con un indirizzo non allineato, causerà un errore di segmentazione e l'applicazione si bloccherà. È anche importante assicurarsi che l'allineamento corrisponda alla dimensione del registro SIMD, quindi 16 per xmm, 32 per ymm e 64 per zmm. +In FFmpeg, `av_malloc` è in grado di provvedere memoria allineata nell'*heap* e la direttiva preprocessore `DECLARE_ALIGNED C` ci può dare memoria allineata nello *stack*. Se `mova` è utilizzato con un indirizzo non allineato, causerà un errore di segmentazione e l'applicazione si bloccherà. È anche importante assicurarsi che l'allineamento corrisponda alla dimensione del registro SIMD, quindi 16 per xmm, 32 per ymm e 64 per zmm. -Ecco come allineiamo l'inizio della sezione RODATA a 64 byte +Ecco come allineiamo l'inizio della sezione `RODATA` a 64 byte ```assembly SECTION_RODATA 64 ``` -Da notare come questo allinei soltanto l'inizio di RODATA. Byte di riempimento potrebbero essere necessari per essere sicuri che la prossima etichetta rimanga entro il limite di 64 byte. +Da notare come questo allinei soltanto l'inizio di `RODATA`. Byte di riempimento potrebbero essere necessari per essere sicuri che la prossima etichetta rimanga entro il limite di 64 byte. **Espansione della portata** @@ -126,13 +126,13 @@ Diamo un occhio a come funziona `punpcklbw`. La sintassi per la versione SSE2 è Questo significa che la sua fonte (a destra) può essere un registro xmm o un indirizzo di memoria (m128 significa un indirizzo di memoria con la sintassi `[base + scala*indice + spiazzamento]`) e come destinazione un registro xmm. -il sito [officedaytime.com](officedaytime.com) ha un buon diagramma per mostrare ciò che sta succedendo +Il sito [officedaytime.com](officedaytime.com) ha un buon diagramma per mostrare ciò che sta succedendo ![cos'è questo](image1.png) -Puoi vedere come i byte sono alternati dalla parte più bassa di ogni registro rispettivamente. Ma che c'entra con le espansioni? Se il registro src è tutti zero questo alterna i byte in dst con zeri. Questo è conosciuto come una *zero extension* (estensione a zero) in quanto i byte sono senza segno. punpckhbw può essere usato per fare la stessa cosa per i byte alti. +Puoi vedere come i byte sono alternati dalla parte più bassa di ogni registro rispettivamente. Ma che c'entra con le espansioni? Se il registro `src` è tutti zero questo alterna i byte in `dst` con zeri. Questo è conosciuto come una *zero extension* (estensione a zero) in quanto i byte sono senza segno. `punpckhbw` può essere usato per fare la stessa cosa per i byte alti. -Ecco un esempio che mostra come funziona questa cosa: +Ecco un esempio che mostra come si fa questa cosa: ```assembly pxor m2, m2 ; azzera m2 @@ -147,9 +147,9 @@ punpckhbw m1, m2 **Estensione del segno** -I dati con segno sono un rendono le cose un pò più complicate. Per espandere la portata di un intero con segno, dobbiamo usare in processo conosciuto come [Estensione del Segno](https://en.wikipedia.org/wiki/Sign_extension). Questo imbottisce i bit più significativo il bit di segno. Per esempio: -2 in `int8_t` è pari a `0b11111110`. Per effettuare l'estensione del segno e renderlo un `int16_t` il bit più significativo viene ripetuto, per risultare `0b1111111111111110`. +I dati con segno rendono le cose un po' più complicate. Per espandere la portata di un intero con segno, dobbiamo usare un processo conosciuto come [Estensione del Segno](https://en.wikipedia.org/wiki/Sign_extension). Questo imbottisce il bit più significativo con il bit di segno. Per esempio: -2 in `int8_t` è pari a `0b11111110`. Per effettuare l'estensione del segno e renderlo un `int16_t` il bit più significativo viene ripetuto, per risultare `0b1111111111111110`. -`pcmpgtb` (packed compare greater than byte) può essere confuso per l'estensione del segno. Facendo il confronto (0 > byte), tutti i bit nella destinazione sono 1 se il byte è negativo, altrimenti i bit alla destinazione vengono tutti impostati a 0. `punpckX` può essere usato come sopra riportato per eseguire l'estensione del segno. Se il byte è negativo il byte corrispondente è `0b11111111` sennò altrimenti è `0x00000000`. Alternare il valore in byte con il risultato di `pcmgtb` esegue un estensione del segno come risultato. +`pcmpgtb` (packed compare greater than byte) può essere confuso per l'estensione del segno. Facendo il confronto (0 > byte), tutti i bit nella destinazione sono 1 se il byte è negativo, altrimenti i bit nella destinazione vengono impostati a 0. `punpckX` può essere usato come sopra riportato per eseguire l'estensione del segno. Se il byte è negativo il byte corrispondente è `0b11111111`, altrimenti è `0x00000000`. Alternare il valore in byte con il risultato di `pcmgtb` esegue un estensione del segno come risultato. ```assembly pxor m2, m2 ; zero out m2 @@ -164,13 +164,13 @@ punpckhbw m1, m2 Come puoi vedere c'è un istruzione in più in confronto al caso senza segno. -**impacchettamento (Packing)** +**Impacchettamento (Packing)** -`packuswb` (pack unsigned word to byte) e `packsswb` ci permettono di passare da una parola ad un byte. Ci permette di alternare due registri SIMD contenenti due parole in un registro SIMD da un byte. Nota che se i valori eccedono la dimensione del byte, verrano saturati (ovvero limitati al proprio valore massimo). +`packuswb` (pack unsigned word to byte) e `packsswb` ci permettono di passare da parola a byte. Ci permette di alternare due registri SIMD contenenti parole in un registro SIMD da un byte. Nota che se i valori eccedono la dimensione del byte, verrano saturati (ovvero limitati al proprio valore massimo). **Shuffles (Mescolamenti)** -Gli *shuffle*, conosciuti anche come permute, sono discutibilmente le istruzioni più importanti nell'elaborazione video e `pshufb` (packed shuffle bytes), disponibile in SSSE3, è la variante più importante. +Gli *shuffle*, conosciuti anche come pérmute, sono discutibilmente le istruzioni più importanti nell'elaborazione video e `pshufb` (packed shuffle bytes), disponibile in SSSE3, è la variante più importante. Per ogni byte il corrispondente byte sorgente è usato da indice del registro di destinazione, eccetto quando il bit più significativo è zero, il che significa il byte di destinazione è azzerato. È analogo al seguente codice C (anche se in SIMD tutti e 16 le iterazioni del ciclo avvengono in parallelo):