Esistono due tipi di linguaggi di programmazione: linguaggi di programmazione garbage-collect e linguaggi di programmazione che richiedono la gestione manuale della memoria. Esempi del primo, tra molti altri, sono Kotlin, PHP o Java. Esempi di quest'ultimo sono C, C++ o Rust. Come regola generale, i linguaggi di programmazione di alto livello hanno maggiori probabilità di avere la raccolta dei rifiuti come funzionalità standard. In questo post del blog ci concentriamo su questi linguaggi di programmazione garbage-collect e su come possono essere compilati in WebAssembly (Wasm). Ma che cos'è la raccolta dei rifiuti (spesso indicata come GC)?
Supporto dei browser
Garbage collection
In termini semplificati, l'idea di garbage collection è il tentativo di recuperare la memoria allocata dal programma, ma a cui non si fa più riferimento. Questa memoria è chiamata spazzatura. Esistono molte strategie per implementare la garbage collection. Uno di questi è il conteggio dei riferimenti, il cui scopo è contare il numero di riferimenti agli oggetti in memoria. Quando non ci sono più riferimenti a un oggetto, questo può essere contrassegnato come non più utilizzato e quindi pronto per la raccolta dei rifiuti. Il garbage collector di PHP utilizza il conteggio dei riferimenti e la funzione xdebug_debug_zval()
dell'estensione Xdebug ti consente di dare un'occhiata sotto il cofano. Considera il seguente programma PHP.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Il programma assegna un numero casuale trasmesso a una stringa a una nuova variabile chiamata a
. Quindi crea due nuove variabili, b
e c
, e assegna loro il valore di a
. Dopodiché, assegna nuovamente b
al numero 42
e annulla l'impostazione di c
. Infine, imposta il valore di a
su null
. Annotando ogni passaggio del programma con xdebug_debug_zval()
, puoi vedere il contatore dei riferimenti del garbage collector in funzione.
<?php
$a= (string) rand();
$c = $b = $a;
xdebug_debug_zval('a');
$b = 42;
xdebug_debug_zval('a');
unset($c);
xdebug_debug_zval('a');
$a = null;
xdebug_debug_zval('a');
?>
L'esempio precedente restituirà i log seguenti, in cui vedrai come diminuisce il numero di riferimenti al valore della variabile a
dopo ogni passaggio, il che ha senso data la sequenza del codice. Il tuo numero casuale sarà diverso, ovviamente.
a:
(refcount=3, is_ref=0)string '419796578' (length=9)
a:
(refcount=2, is_ref=0)string '419796578' (length=9)
a:
(refcount=1, is_ref=0)string '419796578' (length=9)
a:
(refcount=0, is_ref=0)null
La garbage collection presenta altre sfide, ad esempio i cicli di rilevamento, ma per questo articolo è sufficiente avere un livello base di comprensione del conteggio dei riferimenti.
I linguaggi di programmazione sono implementati in altri linguaggi di programmazione
Potrebbe sembrare un inizio, ma i linguaggi di programmazione sono implementati in altri linguaggi di programmazione. Ad esempio, il runtime PHP è implementato principalmente in C. Puoi controllare il codice sorgente PHP su GitHub. Il codice della raccolta dei rifiuti di PHP si trova principalmente nel file zend_gc.c
. La maggior parte degli sviluppatori installa PHP tramite il gestore dei pacchetti del proprio sistema operativo. Gli sviluppatori possono anche creare PHP dal codice sorgente. Ad esempio, in un ambiente Linux, i passaggi ./buildconf && ./configure && make
compileranno PHP per il runtime Linux. Ciò significa anche che il runtime PHP può essere compilato per altri runtime, ad esempio Wasm.
Metodi tradizionali di porting dei linguaggi al runtime Wasm
Indipendentemente dalla piattaforma su cui è in esecuzione PHP, gli script PHP vengono compilati nello stesso bytecode ed eseguiti dallo Zend Engine. Zend Engine è un compilatore e un ambiente di runtime per il linguaggio di scripting PHP. È costituito dalla Zend Virtual Machine (VM), composta dal compilatore Zend e dall'eseguitore Zend. I linguaggi come PHP, implementati in altri linguaggi di alto livello come C di solito hanno ottimizzazioni che hanno come target architetture specifiche, come Intel o ARM, e richiedono un backend diverso per ogni architettura. In questo contesto, Wasm rappresenta una nuova architettura. Se la VM ha codice specifico per l'architettura, come la compilazione just-in-time (JIT) o ahead-of-time (AOT), lo sviluppatore implementa anche un backend per JIT/AOT per la nuova architettura. Questo approccio ha molto senso perché spesso la parte principale della base di codice può essere semplicemente ricompilata per ogni nuova architettura.
Dato il livello basso di Wasm, è naturale provare lo stesso approccio: ricompila il codice VM principale con il relativo parser, il supporto della libreria, la raccolta dei rifiuti e l'ottimizzatore in Wasm e, se necessario, implementa un backend JIT o AOT per Wasm. Questo è stato possibile fin dall'MVP di Wasm e in molti casi funziona bene nella pratica. Infatti, PHP compilato in Wasm è la base di WordPress Playground. Scopri di più sul progetto nell'articolo Creare esperienze WordPress in-browser con WordPress Playground e WebAssembly.
Tuttavia, PHP Wasm viene eseguito nel browser nel contesto del linguaggio host JavaScript. In Chrome, JavaScript e Wasm vengono eseguiti in V8, il motore JavaScript open source di Google che implementa ECMAScript come specificato in ECMA-262. Inoltre, V8 ha già un garbage collector. Ciò significa che gli sviluppatori che utilizzano, ad esempio, PHP compilato in Wasm, finiscono per inviare al browser un'implementazione del garbage collector del linguaggio portato (PHP) che ha già un garbage collector, il che è uno spreco. È qui che entra in gioco WasmGC.
L'altro problema del vecchio approccio di consentire ai moduli Wasm di creare il proprio GC sulla base della memoria lineare di Wasm è che quindi non c'è interazione tra il garbage collector di Wasm e il garbage collector integrato del linguaggio compilato-to-Wasm, che tende a causare problemi come perdite di memoria e tentativi di raccolta inefficienti. Consentire ai moduli Wasm di riutilizzare il GC integrato esistente evita questi problemi.
Trasferire i linguaggi di programmazione a nuovi runtime con WasmGC
WasmGC è una proposta del WebAssembly Community Group. L'attuale implementazione MVP di Wasm è in grado di gestire solo numeri, ovvero interi e numeri in virgola mobile, nella memoria lineare e, con la proposta di tipi di riferimento in fase di implementazione, Wasm può anche conservare i riferimenti esterni. WasmGC ora aggiunge tipi di heap struct e array, il che significa che supporta l'allocazione di memoria non lineare. Ogni oggetto WasmGC ha un tipo e una struttura fissi, che consentono alle VM di generare facilmente un codice efficiente per accedere ai propri campi senza il rischio di deottimizzazioni tipiche dei linguaggi dinamici come JavaScript. Questa proposta aggiunge quindi a WebAssembly un supporto efficiente per i linguaggi gestiti di alto livello, tramite tipi di heap di struct e array che consentono ai compilatori di linguaggi che hanno come target Wasm di integrarsi con un garbage collector nella VM host. In termini semplificati, ciò significa che con WasmGC, il porting di un linguaggio di programmazione a Wasm significa che il garbage collector del linguaggio di programmazione non deve più far parte del porting, ma può essere utilizzato il garbage collector esistente.
Per verificare l'impatto reale di questo miglioramento, il team Wasm di Chrome ha compilato versioni del benchmark Fannkuch (che assegna le strutture dei dati man mano che funzionano) da C, Rust e Java. I file binari C e Rust possono variare da 6,1 K a 9,6 K a seconda dei vari flag del compilatore, mentre la versione Java è molto più piccola, con solo 2,3 K. C e Rust non includono un garbage collector, ma includono comunque malloc/free
per gestire la memoria. Il motivo per cui Java è più piccolo in questo caso è che non ha bisogno di includere alcun codice di gestione della memoria. Questo è solo un esempio specifico, ma dimostra che i binari WasmGC hanno il potenziale di essere molto piccoli, e questo prima ancora di qualsiasi lavoro significativo sull'ottimizzazione delle dimensioni.
Vedere un linguaggio di programmazione portato da WasmGC in azione
Wasm di Kotlin
Uno dei primi linguaggi di programmazione a cui è stato trasferito in Wasm grazie a WasmGC è Kotlin, sotto forma di Kotlin/Wasm. La demo, con il codice sorgente gentilmente concesso dal team di Kotlin, è mostrata nella seguente scheda.
import kotlinx.browser.document
import kotlinx.dom.appendText
import org.w3c.dom.HTMLDivElement
fun main() {
(document.getElementById("warning") as HTMLDivElement).style.display = "none"
document.body?.appendText("Hello, ${greet()}!")
}
fun greet() = "world"
Ora potresti chiederti qual è il punto, dato che il codice Kotlin riportato sopra consiste essenzialmente nelle API OM JavaScript convertite in Kotlin. Inizia ad avere più senso in combinazione con Compose Multiplatform, che consente agli sviluppatori di basarsi sull'interfaccia utente che potrebbero aver già creato per le loro app Android Kotlin. Dai un'occhiata a un'esplorazione preliminare di questa funzionalità con la demo del visualizzatore di immagini Kotlin/Wasm ed esplora il relativo codice sorgente, sempre per gentile concessione del team di Kotlin.
Dart e Flutter
Anche i team di Dart e Flutter di Google stanno preparando il supporto per WasmGC. La compilazione della compilation Dart-to-Wasm è quasi completa e il team sta lavorando al supporto degli strumenti per la distribuzione delle applicazioni web Flutter compilate in WebAssembly. Per informazioni sullo stato attuale del lavoro, consulta la documentazione Flutter. La seguente demo è l'anteprima di Flutter WasmGC.
Scopri di più su WasmGC
Questo post del blog ha appena scalfito la superficie e ha fornito principalmente una panoramica generale di WasmGC. Per saperne di più sulla funzionalità, consulta questi link:
- Un nuovo modo per portare in modo efficiente i linguaggi di programmazione con garbage collection in WebAssembly
- Panoramica di WasmGC
- MVP WasmGC
- WasmGC post-MVP
Ringraziamenti
Questo articolo è stato scritto da Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.