Há dois tipos de linguagens de programação: as que têm coleta de lixo e as que exigem gerenciamento manual de memória. Exemplos do primeiro, entre muitos outros, são Kotlin, PHP ou Java. Exemplos disso são C, C++ ou Rust. Como regra geral, linguagens de programação de nível mais alto têm mais chances de ter coleta de lixo como um recurso padrão. Nesta postagem do blog, o foco está nessas linguagens de programação com coleta de lixo e como elas podem ser compiladas para WebAssembly (Wasm). Mas o que é a coleta de lixo (geralmente chamada de GC)?
Browser Support
Coleta de lixo
Em termos simplificados, a ideia da coleta de lixo é tentar recuperar a memória alocada pelo programa, mas que não é mais referenciada. Essa memória é chamada de lixo. Há muitas estratégias para implementar a coleta de lixo. Uma delas é a contagem de referências, em que o objetivo é contar o número de referências a objetos na memória. Quando não há mais referências a um objeto, ele pode ser marcado como não usado e, portanto, pronto para coleta de lixo. O coletor de lixo do PHP usa a contagem de referências, e o uso da função xdebug_debug_zval()
da extensão Xdebug permite que você veja como ele funciona. Considere o seguinte programa em PHP.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
O programa atribui um número aleatório convertido em uma string a uma nova variável chamada a
. Em seguida, ele cria duas novas variáveis, b
e c
, e atribui a elas o valor de a
. Depois disso, ele reatribui b
ao número 42
e desativa c
. Por fim, ele define o valor de a
como null
. Ao anotar cada etapa do programa com xdebug_debug_zval()
, é possível ver o contador de referência do coletor de lixo em ação.
<?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');
?>
O exemplo acima vai gerar os seguintes registros, em que você vê como o número de referências ao valor da variável a
diminui após cada etapa, o que faz sentido considerando a sequência de código. Seu número aleatório será diferente, é claro.
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
Existem outros desafios com a coleta de lixo, como detecção de ciclos, mas, para este artigo, ter um nível básico de compreensão da contagem de referências é suficiente.
As linguagens de programação são implementadas em outras linguagens de programação
Pode parecer estranho, mas as linguagens de programação são implementadas em outras linguagens de programação. Por exemplo, o ambiente de execução do PHP é implementado principalmente em C. Confira o código-fonte do PHP no GitHub. O código de coleta de lixo do PHP está principalmente no arquivo zend_gc.c
. A maioria dos desenvolvedores instala o PHP pelo gerenciador de pacotes do sistema operacional. Mas os desenvolvedores também podem criar PHP com base no código-fonte. Por exemplo, em um ambiente Linux, as etapas ./buildconf && ./configure && make
criariam o PHP para o ambiente de execução do Linux. Mas isso também significa que o ambiente de execução do PHP pode ser compilado para outros ambientes, como, você adivinhou, o Wasm.
Métodos tradicionais de portabilidade de linguagens para o ambiente de execução do Wasm
Independente da plataforma em que o PHP está sendo executado, os scripts PHP são compilados no mesmo bytecode e executados pelo Zend Engine. O Zend Engine é um compilador e um ambiente de execução para a linguagem de programação PHP. Ele consiste na máquina virtual (VM) Zend, que é composta pelo Zend Compiler e pelo Zend Executor. Linguagens como PHP, que são implementadas em outras linguagens de alto nível, como C, geralmente têm otimizações que segmentam arquiteturas específicas, como Intel ou ARM, e exigem um back-end diferente para cada arquitetura. Nesse contexto, o Wasm representa uma nova arquitetura. Se a VM tiver um código específico da arquitetura, como compilação just-in-time (JIT) ou ahead-of-time (AOT), o desenvolvedor também vai implementar um back-end para JIT/AOT na nova arquitetura. Essa abordagem faz muito sentido porque, geralmente, a parte principal do codebase pode ser recompilada para cada nova arquitetura.
Como o Wasm é de baixo nível, é natural tentar a mesma abordagem: recompilar o código principal da VM com o analisador, o suporte à biblioteca, a coleta de lixo e o otimizador para Wasm e implementar um back-end JIT ou AOT para Wasm, se necessário. Isso é possível desde o MVP do Wasm e funciona bem na prática em muitos casos. Na verdade, o PHP compilado para Wasm é o que alimenta o WordPress Playground. Saiba mais sobre o projeto no artigo Crie experiências do WordPress no navegador com o WordPress Playground e o WebAssembly.
No entanto, o PHP Wasm é executado no navegador no contexto da linguagem host JavaScript. No Chrome, o JavaScript e o Wasm são executados no V8, o mecanismo JavaScript de código aberto do Google que implementa o ECMAScript conforme especificado no ECMA-262. Além disso, o V8 já tem um coletor de lixo. Isso significa que os desenvolvedores que usam, por exemplo, PHP compilado para Wasm, acabam enviando uma implementação de coletor de lixo da linguagem portável (PHP) para o navegador, que já tem um coletor de lixo. Isso é tão ineficiente quanto parece. É aí que o WasmGC entra em cena.
Outro problema da abordagem antiga de permitir que os módulos Wasm criem seu próprio GC sobre a memória linear do Wasm é que não há interação entre o coletor de lixo do Wasm e o coletor de lixo criado sobre a linguagem compilada para Wasm, o que tende a causar problemas como vazamentos de memória e tentativas de coleta ineficientes. Permitir que os módulos Wasm reutilizem o GC integrado evita esses problemas.
Portar linguagens de programação para novos ambientes de execução com WasmGC
O WasmGC é uma proposta do WebAssembly Community Group (em inglês). A implementação atual do MVP do Wasm só consegue lidar com números, ou seja, números inteiros e de ponto flutuante, na memória linear. Com a proposta de tipos de referência sendo enviada, o Wasm também pode manter referências externas. O WasmGC agora adiciona tipos de heap de struct e matriz, o que significa suporte para alocação de memória não linear. Cada objeto WasmGC tem um tipo e uma estrutura fixos, o que facilita a geração de código eficiente pelas VMs para acessar os campos sem o risco de desotimizações que linguagens dinâmicas como JavaScript têm. Assim, essa proposta adiciona suporte eficiente para linguagens gerenciadas de alto nível ao WebAssembly, usando tipos de heap de struct e matriz que permitem que compiladores de linguagem destinados ao Wasm se integrem a um coletor de lixo na VM host. Em termos simplificados, isso significa que, com o WasmGC, a portabilidade de uma linguagem de programação para o Wasm não exige mais que o coletor de lixo da linguagem de programação faça parte da portabilidade. Em vez disso, o coletor de lixo atual pode ser usado.
Para verificar o impacto real dessa melhoria, a equipe do Wasm do Chrome compilou versões do benchmark Fannkuch (que aloca estruturas de dados enquanto funciona) em C, Rust e Java. Os binários C e Rust podem variar de 6,1 K a 9,6 K, dependendo dos vários flags do compilador, enquanto a versão Java é muito menor, com apenas 2,3 K. C e Rust não incluem um coletor de lixo, mas ainda agrupam malloc/free
para gerenciar a memória. O motivo de o Java ser menor aqui é que ele não precisa agrupar nenhum código de gerenciamento de memória. Esse é apenas um exemplo específico, mas mostra que os binários do WasmGC têm o potencial de serem muito pequenos, e isso antes de qualquer trabalho significativo de otimização para tamanho.
Como ver uma linguagem de programação transferida para WasmGC em ação
Kotlin Wasm
Uma das primeiras linguagens de programação portadas para Wasm graças ao WasmGC é o Kotlin na forma de Kotlin/Wasm. A demonstração, com código-fonte cortesia da equipe do Kotlin, é mostrada na listagem a seguir.
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"
Talvez você esteja se perguntando qual é o objetivo, já que o código Kotlin acima consiste basicamente nas APIs do modelo de objeto JavaScript convertidas para Kotlin. Isso faz mais sentido em combinação com o Compose Multiplatform, que permite aos desenvolvedores criar com base na interface que já criaram para os apps Kotlin do Android. Confira uma exploração inicial disso com o visualizador de imagens Kotlin/Wasm, também cortesia da equipe do Kotlin.
Dart e Flutter
As equipes do Dart e do Flutter no Google também estão preparando o suporte para o WasmGC. O trabalho de compilação de Dart para Wasm está quase concluído, e a equipe está trabalhando no suporte a ferramentas para entregar aplicativos da Web do Flutter compilados para WebAssembly. Leia sobre o estado atual do trabalho na documentação do Flutter (em inglês). A demonstração a seguir é a prévia do Flutter WasmGC.
Saiba mais sobre o WasmGC
Esta postagem do blog mal arranhou a superfície e ofereceu principalmente uma visão geral de alto nível do WasmGC. Para saber mais sobre o recurso, confira estes links:
- Uma nova maneira de trazer linguagens de programação coletadas de lixo de forma eficiente para o WebAssembly
- Visão geral do WasmGC
- MVP do WasmGC
- WasmGC pós-MVP
Agradecimentos
Este artigo foi revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.