Esistono due tipi di linguaggi di programmazione: linguaggi con garbage collection e linguaggi 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, l'attenzione è rivolta a questi linguaggi di programmazione con garbage collection e a 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 della raccolta dei rifiuti è il tentativo di recuperare la memoria allocata dal programma, ma a cui non viene più fatto 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 a una stringa a una nuova variabile denominata a
. Quindi crea due nuove variabili, b
e c
, e assegna loro il valore di a
. Dopodiché, assegna nuovamente b
al numero 42
e reimposta c
. Infine, imposta il valore di a
su null
. Se annoti ogni passaggio del programma con xdebug_debug_zval()
, puoi vedere in azione il contatore di riferimento del garbage collector.
<?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 riportato sopra genera i seguenti log, in cui puoi vedere come il numero di riferimenti al valore della variabile a
diminuisce dopo ogni passaggio, il che è logico data la sequenza di codice. Ovviamente il tuo numero casuale sarà diverso.
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
Esistono altri problemi con il garbage collection, come il rilevamento dei cicli, ma per questo articolo è sufficiente avere un livello di conoscenza di base 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 dal motore Zend. 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. Linguaggi come PHP, implementati in altri linguaggi di alto livello come C, hanno spesso 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 è possibile dal MVP di Wasm e funziona bene in pratica in molti casi. 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 che consente ai moduli Wasm di creare il proprio GC sulla memoria lineare di Wasm è che non esiste alcuna interazione tra il garbage collector di Wasm e il garbage collector integrato del linguaggio compilato in Wasm, il che tende a causare problemi come perdite di memoria e tentativi di raccolta inefficaci. Consentire ai moduli Wasm di riutilizzare il GC integrato esistente evita questi problemi.
Portare i linguaggi di programmazione a nuove runtime con WasmGC
WasmGC è una proposta del gruppo della community WebAssembly. 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, il che consente alle VM di generare facilmente codice efficiente per accedere ai propri campi senza il rischio di deoptimizations che hanno i linguaggi dinamici come JavaScript. Questa proposta aggiunge quindi a WebAssembly un supporto efficiente per i linguaggi gestiti di alto livello, tramite tipi di heap 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 della porta, 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 di Fannkuch (che alloca le strutture di dati durante il funzionamento) 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.
Linguaggio di programmazione in azione con port a WasmGC
Kotlin Wasm
Uno dei primi linguaggi di programmazione che è stato portato a 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 creare un'interfaccia utente basata su quella 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 esamina 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. Il lavoro di compilazione di Dart in Wasm è quasi completato e il team sta lavorando al supporto degli strumenti per la pubblicazione di applicazioni web Flutter compilate in WebAssembly. Puoi leggere lo stato attuale del lavoro nella documentazione di 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
- WasmGC MVP
- WasmGC post-MVP
Ringraziamenti
Questo articolo è stato esaminato da Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.