Há dois tipos de linguagem de programação: com coleta de lixo e que exigem gerenciamento manual de memória. Kotlin, PHP e Java são exemplos de linguagens de programação. Exemplos do último são C, C++ ou Rust. Como regra geral, as linguagens de programação de nível superior têm mais chances de ter a coleta de lixo como recurso padrão. Neste post 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 (GC, na sigla em inglês)?
Compatibilidade com navegadores
Coleta de lixo
Em termos simplificados, a ideia da coleta de lixo é a tentativa de 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 a coleta de lixo. O coletor de lixo do PHP usa a contagem de referência, e usar a função xdebug_debug_zval()
da extensão Xdebug permite que você dê uma olhada no que está acontecendo. Considere o seguinte programa 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 cancela a configuração de c
. Por fim, ele define o valor de a
como null
. Ao anotar cada etapa do programa com xdebug_debug_zval()
, você poderá 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 registros a seguir, em que você pode conferir 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
Há outros desafios com a coleta de lixo, como detectar ciclos, mas, para este artigo, basta ter um nível básico de compreensão da contagem de referência.
As linguagens de programação são implementadas em outras linguagens de programação
Pode parecer o início, 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. Você pode conferir o código-fonte do PHP no GitHub. O código de coleta de lixo do PHP está localizado principalmente no arquivo zend_gc.c
. A maioria dos desenvolvedores instala o PHP pelo gerenciador de pacotes do sistema operacional. No entanto, os desenvolvedores também podem criar PHP a partir do 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. Isso também significa que o ambiente de execução do PHP pode ser compilado para outros ambientes, como o Wasm.
Métodos tradicionais de portabilidade de linguagens para o ambiente de execução do Wasm
Independentemente da plataforma em que o PHP é executado, os scripts PHP são compilados no mesmo bytecode e executados pelo Zend Engine. O Zend Engine é um ambiente de execução e compilador para a linguagem de script PHP. Ele consiste na máquina virtual (VM) Zend, que é composta pelo compilador Zend e pelo executor Zend. Linguagens como PHP que são implementadas em outras linguagens de alto nível, como C, normalmente têm otimizações direcionadas a 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 antecipada (AOT), o desenvolvedor também implementará um back-end para JIT/AOT para a nova arquitetura. Essa abordagem faz muito sentido porque, muitas vezes, a parte principal da base de código pode ser recompilada para cada nova arquitetura.
Considerando o nível baixo do Wasm, é natural tentar a mesma abordagem: recompilar o código principal da VM com o parser, 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 Criar 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 de destino 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, o PHP compilado para Wasm acabam enviando uma implementação do coletor de lixo da linguagem portada (PHP) para o navegador que já tem um coletor de lixo, o que é tão desperdício quanto parece. É aí que entra o WasmGC.
O outro problema da abordagem antiga de permitir que os módulos Wasm criem sua própria GC sobre a memória linear do Wasm é que não há interação entre o próprio coletor de lixo do Wasm e o coletor de lixo embutido da linguagem compilada para o Wasm, que tende a causar problemas como vazamentos de memória e tentativas ineficientes de coleta. Permitir que os módulos Wasm reutilizem o GC integrado atual evita esses problemas.
Portar linguagens de programação para novos ambientes de execução com o WasmGC
O WasmGC é uma proposta do Grupo da comunidade do WebAssembly. A implementação atual do MVP do Wasm só é capaz de 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ção que as linguagens dinâmicas, como JavaScript, têm. 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 os compiladores de linguagem com destino ao Wasm sejam integrados 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 significa que o coletor de lixo da linguagem de programação não precisa mais fazer parte da portabilidade, mas o coletor de lixo existente pode ser usado.
Para verificar o impacto real dessa melhoria, a equipe do Wasm do Chrome compilou versões do benchmark de Fannkuch (que aloca estruturas de dados conforme funciona) de C, Rust e Java. Os binários C e Rust podem variar de 6.1 K a 9.6 K, dependendo das várias flags do compilador, enquanto a versão Java é muito menor, 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 pelo qual o Java é menor aqui é porque 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 podem ser muito pequenos, mesmo antes de qualquer trabalho significativo de otimização de tamanho.
Como ver uma linguagem de programação com porta de WasmGC em ação
Kotlin Wasm
Uma das primeiras linguagens de programação que foi portada para Wasm graças ao WasmGC é o Kotlin na forma de Kotlin/Wasm. A demonstração, com o código-fonte cortesia da equipe do Kotlin, é mostrada na listagem abaixo.
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 JavaScript OM convertidas em Kotlin. Ele começa a fazer mais sentido em combinação com o Compose Multiplatform, que permite que os desenvolvedores aproveitem a interface que já criaram para os apps Kotlin para Android. Confira uma análise inicial disso com a demonstração do visualizador de imagens Kotlin/Wasm e explore o código-fonte, 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 do Dart para Wasm está quase concluído, e a equipe está trabalhando no suporte a ferramentas para fornecer aplicativos da Web do Flutter compilados para o WebAssembly. Leia sobre o estado atual do trabalho na documentação do Flutter. A demonstração a seguir é a prévia do Flutter WasmGC.
Saiba mais sobre o WasmGC
Esta postagem no blog mal arranhou a superfície e forneceu uma visão geral de alto nível do WasmGC. Para saber mais sobre o recurso, acesse estes links:
- Uma nova maneira de trazer linguagens de programação com coleta 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.