WebAssembly: la versione velocizzata di JavaScript?
Il numero di applicazioni che girano su browser e che, quindi, non sono installate su disco rigido, è in costante crescita. Oltre ai classici software da ufficio come Microsoft 365 e Google Docs, che presentano sempre nuove funzioni, anche i giochi basati su browser si fanno sempre più complessi e richiedono quindi maggiori risorse. Anche se, spesso, tali applicazioni web sono disponibili in JavaScript, un numero sempre crescente di sviluppatori sta invece puntando a WebAssembly, un nuovo tipo di approccio dai risultati davvero sorprendenti.
Cos’è WebAssembly?
WebAssembly (Wasm) è una nuova soluzione a disposizione degli sviluppatori web al fine di creare delle applicazioni disponibili online. Finora l’unica risorsa disponibile in tal senso era JavaScript, un linguaggio che, tuttavia, risulta piuttosto lento e, in alcuni casi, anche limitato. Il World Wide Web Consortium (W3C) ha quindi deciso di avviare lo sviluppo di un nuovo metodo, appunto, il WebAssembly. Per permettere a Wasm di funzionare, anche il relativo browser deve tuttavia essere in grado di gestire il linguaggio in questione ed è per questo che Mozilla (Firefox), Microsoft (Edge), Apple (Safari) e Google (Chrome) hanno partecipato allo sviluppo di Wasm. Su tutte le attuali versioni dei browser dei vari produttori è quindi possibile eseguire le applicazioni in WebAssembly.
Al fine di testare personalmente l’efficienza di WebAssembly vale sicuramente la pena provare Funky Karts. Il gioco, che è a tutti gli effetti un’app mobile, è stato convertito in WebAssembly in modo da poter essere eseguito anche su browser. Nel corso del progetto lo sviluppatore ha anche realizzato un interessante blog, all’interno del quale ha illustrato i singoli passaggi relativi alla conversione.
In linea di principio WebAssembly consiste in bytecode, che possono essere considerati un passaggio intermedio tra un codice macchina, comprensibile soltanto per i computer, e un tipico linguaggio di programmazione che risulta sì comprensibile agli umani, ma che deve essere tuttavia prima compilato. Infatti, se comparato ad altri linguaggi, WebAssembly sembra estremamente veloce, dal momento che il computer impiega pochissime risorse per trasformare il codice di scrittura. Tuttavia, quella di scrivere in bytecode è un’operazione alquanto inusuale: il vantaggio legato a Wasm consiste proprio nel fatto di non dover lavorare con questo linguaggio di programmazione.
In pratica, l’applicazione può essere scritta in C o C++ e il codice sorgente viene poi trasformato tramite l’applicazione Emscripten. Questo strumento era già utilizzato prima dello sviluppo di WebAssembly al fine di trasformare il codice C/C++ in JavaScript (e asm.js). In tal modo, il codice può comunque essere riscritto anche in Wasm, essendo quindi compilato a priori e senza la necessità di essere compilato o interpretato in fase di esecuzione. Infine, nel momento in cui l’utente apre l’applicazione all’interno del browser, si avvia una piccola macchina virtuale all’interno della quale gira l’applicazione.
I vantaggi offerti da WebAssembly
Attualmente WebAssembly presenta un unico svantaggio concreto, ovvero la lentezza legata alla sua diffusione. Infatti, gli sviluppatori web sono abituati a lavorare con JavaScript di cui, in generale, non è prevista la sostituzione. La direzione del progetto WebAssembly dà quindi grande importanza alla comunicazione finalizzata a presentare i vantaggi di Wasm come soluzione aggiuntiva a JavaScript. Tuttavia, grazie al supporto offerto dai maggiori sviluppatori di browser e dal W3C, la diffusione di Wasm sta progressivamente aumentando. Questo è legato anche al fatto che gli utenti, in fase di accesso a un sito, non debbano eseguire autonomamente alcuna operazione: caricare le applicazioni web in WebAssembly risulta infatti semplice quanto in JavaScript, con il vantaggio di essere più veloce.
Dunque, molti sviluppatori che utilizzano già i linguaggi C, C++ o, ad esempio, Rust hanno ora la possibilità di creare direttamente programmi anche per il web. Talvolta i linguaggi di programmazione prevedono anche ulteriori modalità per strutturare l’applicazione. Chi su JavaScript non dovesse trovare le librerie o i framework necessari al proprio programma, avrà ora a disposizione una scelta più ampia per raggiungere il proprio scopo. Gli sviluppatori hanno quindi diversi motivi per considerare più da vicino WebAssembly:
- È uno standard web aperto del W3C
- Offre grandi performance e file poco pesanti
- Risulta perfettamente adattabile anche alla navigazione mobile
- Offre la possibilità teorica di sviluppare anche programmi di realtà virtuale su browser
- Non necessita di un nuovo linguaggio di programmazione
- Consente di utilizzare anche C, C++ o Rust per la programmazione di applicazioni web
- È supportato da tutti i maggiori sviluppatori di browser
- Non presenta alcuna limitazione per gli utenti
WebAssembly nella pratica
Non è prevista la programmazione tramite WebAssembly. Il vantaggio più grande della tecnologia è proprio il poter utilizzare un linguaggio a piacimento come C per poi convertirlo nel potente formato Wasm. Tuttavia può anche risultare sensato lavorare sul codice già compilato e analizzare il funzionamento di WebAssembly.
Il codice sorgente è disponibile in due diverse varianti: WebAssembly Text Format (WAT) e WebAssembly Binary Format (Wasm). L’ultimo è il codice effettivo azionato dalla macchina. Siccome però è costituito esclusivamente da codice binario, è più o meno inutilizzabile per un’analisi umana. Per questo motivo esiste la forma intermedia WAT. Un simile codice impiega espressioni leggibili e può quindi essere studiato dagli stessi programmatori, tuttavia non offre il comfort di lavoro offerto invece da altri linguaggi di programmazione affermati.
Come esempio ci serviamo di un codice sorgente molto semplice in linguaggio C:
#define WASM_EXPORT __attribute__((visibility("default")))
WASM_EXPORT
int main() {
return 1;
}
Il medesimo codice in formato WAT è visibilmente più lungo:
(module
(type $t0 (func))
(type $t1 (func (result i32)))
(func $__wasm_call_ctors (type $t0))
(func $main (export "main") (type $t1) (result i32)
i32.const 1)
(table $T0 1 1 anyfunc)
(memory $memory (export "memory") 2)
(global $g0 (mut i32) (i32.const 66560))
(global $__heap_base (export "__heap_base") i32 (i32.const 66560))
(global $__data_end (export "__data_end") i32 (i32.const 1024)))
A questo punto la leggibilità è già fortemente limitata, ma qualche elemento è individuabile: innanzitutto con WebAssembly è tutto suddiviso in moduli. Questi moduli sono a loro volta suddivisi in funzioni specificate da parametri. Nel complesso si possono riconoscere 5 diversi elementi:
- module: unità più elevata di WebAssembly
- function: raggruppamento all’interno di un modulo
- memory: array con bytes
- global: valore utilizzabile per più moduli
- table: memoria per riferimenti
Se però il codice viene tradotto in forma binaria niente di tutto ciò è più riconoscibile:
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 02 ; num types
; type 0
000000b: 60 ; func
000000c: 00 ; num params
000000d: 00 ; num results
; type 1
000000e: 60 ; func
000000f: 00 ; num params
0000010: 01 ; num results
0000011: 7f ; i32
0000009: 08 ; FIXUP section size
; section "Function" (3)
0000012: 03 ; section code
0000013: 00 ; section size (guess)
0000014: 02 ; num functions
0000015: 00 ; function 0 signature index
0000016: 01 ; function 1 signature index
0000013: 03 ; FIXUP section size
; section "Table" (4)
0000017: 04 ; section code
0000018: 00 ; section size (guess)
0000019: 01 ; num tables
; table 0
000001a: 70 ; funcref
000001b: 01 ; limits: flags
000001c: 01 ; limits: initial
000001d: 01 ; limits: max
0000018: 05 ; FIXUP section size
; section "Memory" (5)
000001e: 05 ; section code
000001f: 00 ; section size (guess)
0000020: 01 ; num memories
; memory 0
0000021: 00 ; limits: flags
0000022: 02 ; limits: initial
000001f: 03 ; FIXUP section size
; section "Global" (6)
0000023: 06 ; section code
0000024: 00 ; section size (guess)
0000025: 03 ; num globals
0000026: 7f ; i32
0000027: 01 ; global mutability
0000028: 41 ; i32.const
0000029: 8088 04 ; i32 literal
000002c: 0b ; end
000002d: 7f ; i32
000002e: 00 ; global mutability
000002f: 41 ; i32.const
0000030: 8088 04 ; i32 literal
0000033: 0b ; end
0000034: 7f ; i32
0000035: 00 ; global mutability
0000036: 41 ; i32.const
0000037: 8008 ; i32 literal
0000039: 0b ; end
0000024: 15 ; FIXUP section size
; section "Export" (7)
000003a: 07 ; section code
000003b: 00 ; section size (guess)
000003c: 04 ; num exports
000003d: 04 ; string length
000003e: 6d61 696e main ; export name
0000042: 00 ; export kind
0000043: 01 ; export func index
0000044: 06 ; string length
0000045: 6d65 6d6f 7279 memory ; export name
000004b: 02 ; export kind
000004c: 00 ; export memory index
000004d: 0b ; string length
000004e: 5f5f 6865 6170 5f62 6173 65 __heap_base ; export name
0000059: 03 ; export kind
000005a: 01 ; export global index
000005b: 0a ; string length
000005c: 5f5f 6461 7461 5f65 6e64 __data_end ; export name
0000066: 03 ; export kind
0000067: 02 ; export global index
000003b: 2c ; FIXUP section size
; section "Code" (10)
0000068: 0a ; section code
0000069: 00 ; section size (guess)
000006a: 02 ; num functions
; function body 0
000006b: 00 ; func body size (guess)
000006c: 00 ; local decl count
000006d: 0b ; end
000006b: 02 ; FIXUP func body size
; function body 1
000006e: 00 ; func body size (guess)
000006f: 00 ; local decl count
0000070: 41 ; i32.const
0000071: 01 ; i32 literal
0000072: 0b ; end
000006e: 04 ; FIXUP func body size
0000069: 09 ; FIXUP section size
; section "name"
0000073: 00 ; section code
0000074: 00 ; section size (guess)
0000075: 04 ; string length
0000076: 6e61 6d65 name ; custom section name
000007a: 01 ; function name type
000007b: 00 ; subsection size (guess)
000007c: 02 ; num functions
000007d: 00 ; function index
000007e: 11 ; string length
000007f: 5f5f 7761 736d 5f63 616c 6c5f 6374 6f72 __wasm_call_ctor
000008f: 73 s ; func name 0
0000090: 01 ; function index
0000091: 04 ; string length
0000092: 6d61 696e main ; func name 1
000007b: 1a ; FIXUP subsection size
0000096: 02 ; local name type
0000097: 00 ; subsection size (guess)
0000098: 02 ; num functions
0000099: 00 ; function index
000009a: 00 ; num locals
000009b: 01 ; function index
000009c: 00 ; num locals
0000097: 05 ; FIXUP subsection size
0000074: 28 ; FIXUP section size
Chi desidera provare a muovere i primi passi in WebAssembly può utilizzare WebAssembly Studio, all’interno del quale troverà un ambiente di sviluppo online per Wasm.