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

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 mais alto têm mais probabilidade de ter a coleta de lixo como um 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

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: não é compatível.

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á por trás. 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, em seguida, desfaz c. Por fim, ele define o valor de a como null. Ao anotar cada etapa do programa com xdebug_debug_zval(), você pode conferir 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 tempo 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á 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 está sendo 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 o PHP, que são implementadas em outras linguagens de alto nível, como C, geralmente têm otimizações voltadas para 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 vai 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. E 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 desperdício quanto parece. É aí que entra o WasmGC.

O outro problema da abordagem antiga de permitir que os módulos do Wasm criem o próprio GC em cima da memória linear do Wasm é que não há interação entre o coletor de lixo do Wasm e o coletor de lixo criado na 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 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. Já 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 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 uma linguagem de programação portada para 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 lista 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 JavaScript OM convertidas em Kotlin. Ele começa a fazer mais sentido em combinação com o Compose Multiplatform, que permite que os desenvolvedores criem a partir da interface que já podem ter criado para os apps Android Kotlin. 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:

Agradecimentos

Este artigo foi revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.