A coleta de lixo do WebAssembly (WasmGC) agora está ativada por padrão no Chrome

Existem dois tipos de linguagens de programação: as de coleta de lixo e as linguagens de programação que exigem gerenciamento manual de memória. Kotlin, PHP ou Java são exemplos dos primeiros, entre muitos outros. Alguns exemplos disso 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. Nesta postagem do blog, o foco são as linguagens de programação que são coletadas de lixo e como elas podem ser compiladas no WebAssembly (Wasm). Mas o que é a coleta de lixo (geralmente chamada de GC)?

Compatibilidade com navegadores

  • Chrome: 119
  • Borda: 119.
  • Firefox: 120
  • Safari: incompatível.

Coleta de lixo

Em termos simplificados, a ideia de 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, assim, pronto para a coleta de lixo. O coletor de lixo do PHP usa a contagem de referência, e o uso da função xdebug_debug_zval() da extensão Xdebug permite que você confira os bastidores. 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 com o nome 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ê vai notar 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 do código. (Seu número aleatório será diferente, obviamente.)

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 a 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 uma ideia inicial, mas as linguagens de programação são implementadas em outras. 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 usando o 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 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, 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 compilador e um ambiente de execução para a linguagem de script PHP. Ele consiste na máquina virtual (VM) Zend, que é composta pelo Zend Compiler e o Zend Executor. 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, na sigla em inglês), o desenvolvedor também implementará um back-end para JIT/AOT na 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 de baixo do Wasm, é natural tentar a mesma abordagem: recompilar o código da VM principal com o analisador, a compatibilidade com a biblioteca, a coleta de lixo e o otimizador para o Wasm e implementar um back-end JIT ou AOT para o Wasm, se necessário. Isso foi possível desde o Wasm MVP e funciona bem na prática em muitos casos. Aliás, a tecnologia compilada em PHP no Wasm (em inglês) é a base do 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 de host JavaScript. No Chrome, JavaScript e Wasm são executados no V8, o mecanismo JavaScript de código aberto do Google que implementa o ECMAScript conforme especificado em 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 de coletor de lixo da linguagem portada (PHP) para o navegador que já tem um coletor de lixo, o que é tão dispendioso 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 evita esses problemas.

Portabilidade de linguagens de programação para novos ambientes de execução com o WasmGC

O WasmGC é uma proposta do WebAssembly Community Group. A implementação atual do Wasm MVP só é capaz de lidar com números, ou seja, números inteiros e flutuantes, na memória linear e, 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 que as VMs gerem códigos eficientes para acessar os campos sem o risco de desotimizações que ocorrem com linguagens dinâmicas como JavaScript. Essa proposta adiciona ao WebAssembly suporte eficiente para linguagens gerenciadas de alto nível, por meio de tipos de heap de struct e matriz que permitem que compiladores de linguagem direcionados ao Wasm se integrem a um coletor de lixo na VM do host. Em termos simplificados, isso significa que, com o WasmGC, transferir uma linguagem de programação para o Wasm significa que o coletor de lixo da linguagem não precisa mais fazer parte da porta, 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 comparativo de mercado do Fannkuch (que aloca estruturas de dados enquanto funciona) do 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 ele mostra que os binários WasmGC têm o potencial de serem muito pequenos, e isso ocorre mesmo antes de qualquer trabalho significativo na otimização do tamanho.

Como ver uma linguagem de programação com porta do WasmGC em ação

Wasm do Kotlin

Uma das primeiras linguagens de programação que foi adaptada para o 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"

Agora você pode estar se perguntando, já que o código Kotlin acima consiste basicamente nas APIs OM do JavaScript 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 explicação inicial sobre isso com a demonstração do visualizador de imagens Kotlin/Wasm e explore o código-fonte (link em inglês), 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 o 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. Esta demonstração é da prévia do WasmGC do Flutter.

Saiba mais sobre o WasmGC

Esta postagem do blog não é suficiente e oferece, na maioria das vezes, uma visão geral de alto nível do WasmGC. Para saber mais sobre o recurso, confira estes links:

Agradecimentos

Imagem principal de Gary Chan no Unsplash. Este artigo foi revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.