Introduzione
Ciao a tutti. Benvenuti a novembre. Spero che stiate bene.
Abbiamo avuto un ottobre super impegnativo. Abbiamo appena lanciato un grande miglioramento alle soluzioni della comunità. Ora le deduplichiamo, così le soluzioni simili vengono mostrate una sola volta; abbiamo aggiunto nuove opzioni di ordinamento, la possibilità di cercare per codice e, se dai un'occhiata a C#, vedrai che abbiamo anche aggiunto la possibilità di filtrare per diversi concetti di programmazione. Così puoi cercare soluzioni che usano lo scorrimento di bit, la ricorsione o qualsiasi altra cosa ti piaccia. Lo estenderemo ad altri track durante la settimana della comunità.
Ma per ora concentriamoci su #12in23. Ottobre è stato un mese interessante, con l'esplorazione dei linguaggi orientati agli oggetti, ma questo mese andiamo sul pesante. Ci concentriamo sui linguaggi assembly e in particolare su MIPS Assembly, x86-64 Assembly e WebAssembly. Come al solito, Erik ci spiegherà cosa rende questi linguaggi interessanti e unici.
I badge
Come sempre, puoi guadagnare il badge Nibbly November completando 5 esercizi qualsiasi in questi linguaggi. Abbiamo anche il badge Year-Long, verso cui so che molti di voi stanno lavorando. Per quello abbiamo 5 esercizi in evidenza da completare. Ecco quali:
- Conteggio dei bit: conta i bit uguali a 1 in un numero
- Chicchi: calcola il numero di chicchi su una scacchiera con caselle che raddoppiano
- Colori delle resistenze: converti il colore di una banda di una resistenza nella sua rappresentazione numerica
- Cifrario rotazionale: implementa il cifrario rotazionale (noto anche come cifrario di Cesare)
- Conteggio dei nucleotidi: calcola quante volte ciascun nucleotide compare in una stringa di DNA
Contesto
Perché chiamarlo Nibbly November?
Beh, come probabilmente saprai, un byte è composto da 8 bit. E un nibble da 4 bit. Il nome inizia ad avere senso se sostituisci la y di «byte» con una i, ottenendo «bite». Un nibble è quindi un piccolo morso.
Che cos'è un linguaggio assembly?
Beh, partiamo dalle basi. Una CPU esegue istruzioni, come «somma due numeri» o «sposta i bit a sinistra», ecc. Queste istruzioni sono note come istruzioni in codice macchina e non sono altro che sequenze specifiche di bit. Eseguire un programma è «semplicemente» (tra virgolette) la CPU che elabora ed esegue queste sequenze di bit.
Dato che scrivere direttamente le istruzioni come sequenze di bit è macchinoso e soggetto a errori, Kathleen e Andrew Donald Booth idearono un linguaggio più adatto agli esseri umani per rappresentare le istruzioni in codice macchina, nientemeno che nel 1947. Un linguaggio di questo tipo, che rappresenta le istruzioni in codice macchina, si chiama linguaggio assembly. Questo linguaggio assembly viene poi convertito in istruzioni in codice macchina tramite un «assembler».
Un aspetto interessante dei linguaggi assembly è che sono legati direttamente a un'architettura della CPU, anche se di solito sono indipendenti dal sistema operativo.
A proposito, se ti ricordi le schede perforate, quei grandi pezzi di cartoncino usati nei primissimi computer per fargli eseguire i programmi, erano anch'esse una forma di linguaggio assembly!
In che cosa il linguaggio assembly è diverso dai linguaggi con cui programmiamo oggi?
La differenza fondamentale è che i linguaggi assembly sono molto a basso livello. Ti mancheranno molte astrazioni a cui ti sei abituato. Di classi o oggetti non c'è traccia. I cicli? Dovrai scriverli a mano tramite salti. Le funzioni? Nemmeno!
Scrivere codice assembly mi ha fatto sentire molto umile, perché ti rendi conto di quanto i linguaggi moderni ti semplifichino la vita. Ma è anche incredibilmente utile scrivere codice assembly, perché acquisisci una comprensione molto migliore di come funzionano davvero le cose.
Curiosità: il 99% del codice sorgente di RollerCoaster Tycoon era codice assembly scritto a mano! È un risultato strabiliante, che apprezzerai ancora di più dopo aver scritto un po' di assembly per conto tuo.
C'è ancora chi scrive codice assembly?
Beh, meno di una volta. Un tempo l'assembly scritto a mano superava spesso il codice macchina generato dai compilatori (ed è una delle ragioni per cui il linguaggio C++ permette di incorporare direttamente codice assembly), ma i compilatori sono diventati così bravi a generare codice macchina che ormai è raramente vero. Detto questo, troverai ancora il linguaggio assembly usato in ambienti in cui le prestazioni sono critiche o le risorse limitate.
Panoramiche
MIPS
- MIPS (Microprocessor without Interlocked Pipelined Stages) è una famiglia di architetture di set di istruzioni per computer a set di istruzioni ridotto (RISC)
- Sviluppato da MIPS Computer Systems e pubblicato per la prima volta nel 1985
- Diverse versioni: MIPS I, II, III, IV, V e MIPS32/64. Le prime due versioni erano solo a 32 bit, ma MIPS III ha introdotto il supporto a 64 bit.
- Diverse estensioni opzionali, come le istruzioni SIMD e la compressione
- Molto influente sulle architetture RISC successive
- Nel 2021 MIPS ha annunciato che l'architettura MIPS non era più in sviluppo e che era migrata a RISC-V (un'architettura open source e senza royalty)
- Usato soprattutto in sistemi embedded (ad esempio i router) e server (i computer Silicon Graphics lo usavano, ed erano famosi per gli effetti speciali nei film), nel supercomputer NEC Cenju-4, nell'auto Tesla Model S, nella sonda New Horizons della NASA, ma anche per insegnare l'assembly nelle università e in diverse console per videogiochi (ad esempio la PlayStation originale, la PlayStation Portable e il Nintendo 64)
x86-64 Assembly
- Progettato da AMD e pubblicato nel 1999 come architettura AMD64
- È una versione a 64 bit del set di istruzioni x86, che risale al 1978, quando Intel lanciò il suo microprocessore 8086. Era un processore a 16 bit, ma in seguito l'80386 aggiunse istruzioni a 32 bit, e quel set di istruzioni è diventato sinonimo di x86.
- La cosa fondamentale che i 64 bit hanno reso possibile è indirizzare più memoria (l'indirizzamento a 32 bit è limitato a 4 GB), che era diventata un collo di bottiglia. In teoria i 64 bit possono indirizzare 16 exabyte, ma attualmente si usano solo 48 bit, il che permette di indirizzare 256 TB (in futuro si potrà estendere quando servirà)
- AMD64 estende il set di istruzioni x86 ed è stato progettato per essere pienamente compatibile con le applicazioni a 16 e 32 bit esistenti tramite una modalità di compatibilità
- Intel aveva progettato l'IA-64 senza coinvolgere AMD. Era un set di istruzioni a 64 bit nuovo, molto diverso e retroincompatibile. Alla fine ha vinto AMD64 e Intel ha implementato la propria versione (con solo minime differenze semantiche)
- Usato ovunque. Dalle workstation ai server (compresi i supercomputer), dai sistemi embedded alle console per videogiochi (ad esempio PS5 e Xbox Series X).
WebAssembly
- Progettato dal W3C, un ente di standardizzazione per le tecnologie web
- Gli obiettivi di progettazione sono:
- Veloce, sicuro e portabile
- Rappresentazione efficiente e portabile
- Un tempo, l'esecuzione veloce sul web si otteneva di solito tramite plugin specializzati per il browser, come Flash e Silverlight, dato che JavaScript di per sé non si presta molto bene al calcolo ad alte prestazioni. I principali svantaggi di questi plugin erano che di solito avevano molti problemi di sicurezza e non erano standardizzati.
- Mozilla progettò asm.js, un sottoinsieme di JavaScript che mirava a permettere di eseguire codice nel browser con ottime prestazioni, cosa che ottenne tramite la coerenza dei tipi (nessun cambio dinamico di tipo) e l'assenza di garbage collection. I linguaggi potevano quindi compilare in asm.js e ottenere comunque buone prestazioni quando eseguiti sul web. Restava però JS, quindi con dei limiti su ciò che si poteva ottenere. Da qui la proposta di un nuovo linguaggio: WASM.
- Un linguaggio simile all'assembly, nel senso che fornisce un insieme di istruzioni da eseguire. Cosa cruciale, non è legato a una CPU specifica, quindi è indipendente dalla piattaforma e richiede un'implementazione per ogni piattaforma (una macchina virtuale). Questo significa che WebAssembly è in realtà bytecode, non codice macchina
- Tipizzato staticamente (differenza cruciale rispetto a JS)
- Di solito usa la compilazione ahead-of-time o just-in-time (ma può essere interpretato)
- Standard aperto e definisce due cose:
- Un formato binario
- Un formato testuale (che viene compilato nel formato binario)
- Implementazioni in tutti i principali browser
- Usato in molte pagine web che richiedono alte prestazioni, come Google Earth, Figma, Unity e Autocad. Sta guadagnando terreno anche lato server, ad esempio per eseguire microservizi, girare su piattaforme SaaS (ad esempio CloudFlare workers) o in Docker
E dal punto di vista della programmazione, in che cosa differiscono?
MIPS
- Usa un'architettura load-store (nota anche come registro-registro), in cui le istruzioni o accedono alla memoria o fanno aritmetica, ma operano esclusivamente sui dati nei registri
x86-64 Assembly
- Usa un'architettura registro-memoria, che permette di eseguire operazioni sulla memoria (o dalla memoria), oltre che sui registri
WebAssembly
- Usa la programmazione basata su stack (nessun registro), con la possibilità di leggere dati dalla memoria e di scriverli in memoria
Che cosa rende speciali questi linguaggi?
MIPS
- Compatto. Il set di istruzioni MIPS riesce a stare tutto su una sola pagina
- Convenzioni di chiamata consolidate aiutano a capire come usare i registri disponibili, ad esempio quali usare per passare gli argomenti e quali per restituire i risultati.
- Stabile. L'ultima versione è stata pubblicata nel 2014
- Ampiamente documentato, soprattutto nei testi accademici
- Molto usato nel mondo reale. Miliardi di dispositivi
x86-64 Assembly
- Pur essendo un'estensione di x64, sono state aggiunte molte funzionalità nuove, tra cui:
- Supporto agli interi a 64 bit
- Registri aggiuntivi
- Istruzioni SSE (istruzioni vettoriali)
- Accesso relativo ai dati (più efficiente quando si usano librerie condivise)
- Bit No-Execute (funzionalità di sicurezza per impedire l'esecuzione di codice in determinate pagine di memoria)
- Familiare. Dato che estende il set di istruzioni x86, sarà relativamente facile da imparare per chi ha familiarità con il set di istruzioni x86 Documentazione ampia e dettagliata
- Stabile. Sebbene vengano aggiunte regolarmente nuove versioni, il nucleo resta estremamente stabile e retrocompatibile
WebAssembly
- La macchina virtuale WebAssembly basata su stack è snella e semplice rispetto ai linguaggi assembly dei processori fisici (RISC compreso). Questo la rende relativamente facile come destinazione di compilazione.
- Il WebAssembly Text Format usa lo «zucchero» delle S-Expression per ottenere uno stile imperativo familiare, che viene trasformato in codice basato su stack. La forma con le S-Expression è detta «forma zuccherata» e si «dezucchera» nell'altra forma, equivalente a ciò che c'è nel binario. Le S-expression saranno familiari a chiunque abbia mai lavorato con un LISP
- Forte interoperabilità con JavaScript. Semplice passare dati da e verso JavaScript. Avvertenza importante: WASM non permette (ancora) di interagire con il DOM
- In continuo miglioramento. Non solo le macchine virtuali WASM vengono migliorate, ma anche lo standard stesso è in sviluppo attivo. Un sacco di nuove funzionalità vengono progettate e sviluppate, come istruzioni legate a SIMD, garbage collection, thread, ottimizzazioni delle chiamate in coda e altro ancora
- Sicuro. Il codice viene convalidato ed eseguito in un ambiente isolato (sandbox), offrendo un grado di convalida statica superiore rispetto a JavaScript o ai linguaggi assembly nativi. La semantica è ben definita, il che rende facile verificarli e ragionarci sopra
Funzionalità che spiccano
MIPS
- Efficienza. I processori MIPS sono molto efficienti, il che li rende ottimi per i sistemi embedded.
- Prestazioni. Ottime prestazioni, ed è per questo che MIPS è stato usato nei supercomputer
- Facile da imparare. Con poche istruzioni e ciascuna che fa una sola cosa semplice, è facile da imparare. Ottimo per scopi didattici.
x86-64 Assembly
- Potente. x86-64 è stato messo a punto nel corso di decenni, con moltissime istruzioni che aiutano le prestazioni. Un esempio è SIMD (Single Instruction, Multiple Data), istruzioni che, beh, permettono a una singola istruzione di operare in parallelo su più dati.
- Onnipresente. I dispositivi che eseguono x86-64 sono ovunque. Le CPU Intel e AMD lo implementano. È lo standard di fatto da parecchio tempo
- Aggiornato regolarmente. Ad esempio nuove istruzioni vettoriali, con SSE3-5, AVX, AVX-512 e altro
WebAssembly
- Efficiente. Il formato binario è compatto e può essere decodificato, convalidato e compilato in una sola passata veloce. È anche gestibile in streaming, il che permette di iniziare la decodifica, la convalida e la compilazione il prima possibile, prima di aver visto tutti i dati. Ed è anche parallelizzabile. Questo lo rende perfetto per le applicazioni web ad alte prestazioni.
- Ottima destinazione di compilazione. Permette di eseguire sul web codice scritto in molti linguaggi. La maggior parte dei linguaggi principali supporta la compilazione in binari WebAssembly, il che significa che il tuo codice può girare sul web senza dover scrivere JavaScript. Alcuni linguaggi non compilano il tuo codice in WebAssembly, ma compilano il runtime in WebAssembly, che poi può eseguire bytecode non modificato.
- Facile da rilasciare ed eseguire. Tutto ciò che serve è una macchina virtuale in grado di interpretare il bytecode, e tutti i principali browser ne includono una.
- Non è legato al web, ma può girare anche lato server. WebAssembly System Interface (WASI) è un'interfaccia (ABI e API) progettata per essere portabile su qualsiasi piattaforma. È simile a POSIX (interfacce standard per i sistemi Unix) e fornisce cose come l'I/O. La sicurezza è una parte fondamentale della sua progettazione, che include il sandboxing e l'orientamento alle capability (devi chiedere esplicitamente i permessi per cose come file o socket). WASI ha anche il potenziale per permettere un facile interfacciamento tra linguaggi. Solomon Hykes, cofondatore di Docker, ha scritto nel 2019: «Se WASM+WASI fossero esistiti nel 2008, non avremmo avuto bisogno di creare Docker»
Quale scegliere
- Se non hai mai lavorato con un linguaggio assembly, WebAssembly è probabilmente il più facile con cui iniziare. Detto questo, se vuoi imparare un linguaggio assembly che compila in codice macchina, prova MIPS Assembly
- Se lavori su una macchina x86-64 (molto probabile), prova x86-64 Assembly
- Se hai familiarità con LISP, apprezzerai che WebAssembly usi le S-expression
- Se lavori con app web, WebAssembly è l'opzione più logica
- Se ti interessano le prestazioni, x86-64 e MIPS sono ottime opzioni. Oppure, se ti interessano le prestazioni sul web, prova WebAssembly