Esistono due tipi di linguaggi di programmazione: linguaggi di programmazione garbage-collect e linguaggi di programmazione che richiedono la gestione manuale della memoria. I primi esempi sono, tra gli altri, Kotlin, PHP o Java. Esempi di questi ultimi sono C, C++ o Rust. Come regola generale, è più probabile che i linguaggi di programmazione di livello superiore dispongano di una funzionalità standard di garbage collection. In questo post del blog ci concentriamo su questi linguaggi di programmazione garbage-collect e su come possono essere compilati in WebAssembly (Wasm). Ma cos'è per iniziare la garbage collection (spesso denominata 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. Una tale memoria è chiamata spazzatura. Esistono molte strategie per implementare la garbage collection. Una di queste è il conteggio dei riferimenti, in cui l'obiettivo è contare il numero di riferimenti agli oggetti in memoria. Quando non ci sono altri riferimenti a un oggetto, può essere contrassegnato come non più utilizzato e quindi pronto per la garbage collection. Il garbage collector di PHP utilizza il conteggio dei riferimenti e la funzione xdebug_debug_zval()
dell'estensione Xdebug ti consente di eseguire una ricerca "dietro le quinte". 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
. Crea quindi due nuove variabili, b
e c
, e assegna loro il valore a
. Dopodiché, riassegna b
al numero 42
, quindi 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 di 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 il rilevamento dei cicli, 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
Può sembrare un concetto iniziale, 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 di garbage collection di PHP si trova principalmente nel file zend_gc.c
. La maggior parte degli sviluppatori installa PHP tramite il gestore di pacchetti del proprio sistema operativo. Tuttavia, gli sviluppatori possono anche creare PHP dal codice sorgente. Ad esempio, in un ambiente Linux, i passaggi ./buildconf && ./configure && make
creeranno PHP per il runtime Linux. Questo significa anche che il runtime PHP può essere compilato per altri runtime, come, ad esempio, Wasm.
Metodi tradizionali di portabilità delle lingue nel 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 ambiente di compilazione e runtime per il linguaggio di scripting PHP. È costituita dalla macchina virtuale (VM) Zend, composta dal compilatore Zend e dallo Zend Executor. 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 un codice specifico dell'architettura, come la compilazione just-in-time (JIT) o anticipata (AOT), lo sviluppatore implementa anche un backend per JIT/AOT per la nuova architettura. Questo approccio ha molto senso perché spesso la parte principale del codebase può essere semplicemente ricompilata per ogni nuova architettura.
Considerato il livello di basso livello di Wasm, è naturale provare lo stesso approccio: ricompila il codice della VM principale con il relativo parser, il supporto della libreria, la garbage collection 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, i file PHP compilati in Wasm sono alla base di WordPress Playground. Scopri di più sul progetto nell'articolo Creare esperienze WordPress integrate nel browser con WordPress Playground e WebAssembly.
Tuttavia, PHP Wasm viene eseguito nel browser nel contesto del codice JavaScript del linguaggio host. 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 spedire un'implementazione del garbage collector del linguaggio ported (PHP) al browser che ha già un garbage collector, che è uno spreco come sembra. È 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. Se consenti ai moduli Wasm di riutilizzare il GC integrato esistente, si evitano questi problemi.
Trasferire i linguaggi di programmazione a nuovi runtime con WasmGC
WasmGC è una proposta del WebAssembly Community Group. L'attuale implementazione di Wasm MVP è in grado di gestire solo i numeri, ovvero numeri interi e in virgola mobile, nella memoria lineare. Dato che la proposta con i tipi di riferimento viene spedita, Wasm può inoltre conservare i riferimenti esterni. WasmGC ora aggiunge tipi di heap struct e array, il che significa che supporta l'allocazione della 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 linguaggi gestiti di alto livello, tramite tipi di heap di struct e array che consentono ai compilatori di linguaggio che hanno come target Wasm di integrarsi con un garbage collector nella VM host. In termini semplificati, ciò significa che con WasmGC, il trasferimento 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 Fannkuch (che assegna le strutture dei dati man mano che funzionano) da C, Rust e Java. I file binari C e Rust potrebbero variare da 6.1 K a 9.6 K a seconda dei vari flag del compilatore, mentre la versione Java è molto più piccola a soli 2.3 K! C e Rust non includono un garbage collector, ma raggruppano comunque malloc/free
per gestire la memoria. Il motivo per cui Java è più piccolo in questo caso è che non ha bisogno di raggruppare alcun codice di gestione della memoria. Questo è solo un esempio specifico, ma dimostra che i file binari WasmGC hanno il potenziale per essere molto piccoli, e questo è ancor prima di qualsiasi lavoro significativo di ottimizzazione in base alle dimensioni.
Vedere un linguaggio di programmazione portato da WasmGC in azione
Wasm di Kotlin
Uno dei primi linguaggi di programmazione a essere stato trasferito su Wasm grazie a WasmGC è Kotlin sotto forma di Kotlin/Wasm. La demo, con codice sorgente per gentile concessione del team Kotlin, è mostrata nell'elenco che segue.
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 sopra indicato è costituito essenzialmente da API JavaScript OM convertite in Kotlin. Inizia ad avere più senso in combinazione con Compose Multiplatform, che consente agli sviluppatori di sviluppare sulla UI che potrebbero aver già creato per le loro app Android Kotlin. Dai un'occhiata a un'anteprima di questo argomento con la demo del visualizzatore di immagini Kotlin/Wasm ed esplora il relativo codice sorgente, gentilmente concessi dal team di Kotlin.
Freccette e Flutter
I team Dart e Flutter di Google stanno inoltre 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 demo seguente è l'anteprima di Flutter WasmGC.
Scopri di più su WasmGC
Questo blog post ha appena scalfito la superficie e ha fornito per lo più una panoramica generale di WasmGC. Per saperne di più su questa funzionalità, dai un'occhiata a questi link:
- Un nuovo modo per utilizzare in WebAssembly i linguaggi di programmazione garbage collection in modo efficiente
- Panoramica di WasmGC
- MVP WasmGC
- WasmGC post-MVP
Ringraziamenti
Immagine hero di Gary Chan su Unsplash. Questo articolo è stato scritto da Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.