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

Há dois tipos de linguagens de programação: as coletadas por coleta de lixo e as linguagens de programação que exigem gerenciamento manual de memória. Exemplos do primeiro e muitos outros são Kotlin, PHP ou Java. Alguns exemplos são C, C++ ou Rust. Como regra geral, 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 coletadas de lixo e como elas podem ser compiladas no WebAssembly (Wasm). Mas o que é a coleta de lixo (muitas vezes chamada de GC) para começar?

Compatibilidade com navegadores

  • 119
  • 119
  • 120
  • x

Coleta de lixo

Em termos simplificados, a ideia da coleta de lixo é a tentativa de recuperar a memória que foi 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 mais e, portanto, pronto para a 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ê escute os detalhes. Considere o programa PHP a seguir.

<?php
  $a= (string) rand();
  $c = $b = $a;
  $b = 42;
  unset($c);
  $a = null;
?>

O programa atribui um número aleatório transmitido a uma string em 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 definição de c. Por fim, ele define o valor do a como null. Anotando 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 registros abaixo, 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 de acordo com a sequência do 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, 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 incepção, 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á localizado principalmente no arquivo zend_gc.c. A maioria dos desenvolvedores instala o PHP por meio do gerenciador de pacotes do sistema operacional. Mas 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 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 compilador e um ambiente de execução para a linguagem de script PHP. Ele consiste na máquina virtual (VM) do Zend, que é composta pelo Zend Compiler e pelo Zend Executor. Linguagens como PHP implementadas em outras linguagens de alto nível, como C, costumam ter otimizações voltadas 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 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 da VM principal com o analisador, suporte à biblioteca, coleta de lixo e otimizador para o Wasm e implementar um back-end JIT ou AOT para o Wasm, se necessário. Isso já é possível desde o Wasm MVP e funciona bem na prática em muitos casos. Na verdade, o WordPress Playground é o PHP compilado para o Wasm. 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, 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 na ECMA-262. E o V8 já tem um coletor de lixo. Isso significa que os desenvolvedores que fazem uso de PHP compilado para o Wasm, por exemplo, 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 desperdiçado quanto parece. É aí que entra o WasmGC.

O outro problema da abordagem antiga de permitir que os módulos Wasm criem o próprio GC sobre a memória linear do Wasm é que não há interação entre o coletor de lixo do próprio Wasm e o coletor de lixo integrado da linguagem compilada para Wasm, que tende a causar problemas como vazamentos de memória e tentativas de coleta ineficientes. Permitir que os módulos Wasm reutilizem a GC integrada atual evita esses problemas.

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

O WasmGC é uma proposta do grupo da comunidade WebAssembly (links em inglês). A implementação atual do Wasm MVP só é capaz de lidar com números, ou seja, números inteiros e pontos 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. Agora, o WasmGC adiciona tipos de heap de estrutura 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ódigos eficientes pelas VMs para acessar os campos sem o risco de desotimização que linguagens dinâmicas como JavaScript têm. Dessa forma, essa proposta adiciona suporte eficiente a linguagens gerenciadas de alto nível ao WebAssembly, por meio dos tipos de heap de struct e matriz que permitem que os compiladores de linguagem voltados ao Wasm se integrem a um coletor de lixo na VM do 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 porta. 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 comparativo do Fannkuch (que aloca estruturas de dados conforme ele funciona) do C, do Rust e do Java. Os binários C e Rust podem variar de 6.1 K a 9.6 K, dependendo das diversas flags do compilador, enquanto a versão em Java é muito menor, com apenas 2.3 K. C e Rust não incluem um coletor de lixo, mas 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. Este é apenas um exemplo específico, mas mostra que os binários WasmGC têm o potencial de serem muito pequenos, e isso acontece antes de qualquer trabalho significativo na otimização de tamanho.

Como ver uma linguagem de programação portada pelo WasmGC em ação

Wasm do Kotlin

Uma das primeiras linguagens de programação que foram adaptadas para o Wasm graças ao WasmGC é 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 OM do JavaScript convertidas em Kotlin. Ele começa a fazer mais sentido quando combinado com o Compose Multiplatform, que permite aos desenvolvedores criar com base na interface que talvez já tenham criado para os apps Kotlin para Android. Confira uma análise inicial sobre isso com a demonstração do visualizador de imagens Kotlin/Wasm (link em inglês) e conheça o código-fonte (link em inglês) dele. Da mesma forma, uma cortesia da equipe do Kotlin.

Dart e Flutter

As equipes do Dart e do Flutter no Google também estão preparando o suporte ao WasmGC. O trabalho de compilação do Dart-to-Wasm está quase concluído, e a equipe está trabalhando no suporte a ferramentas para fornecer aplicativos da Web do Flutter compilados no WebAssembly. É possível ler sobre o estado atual do trabalho na documentação do Flutter. A demonstração a seguir é a Prévia do WasmGC do Flutter.

Saiba mais sobre o WasmGC

Esta postagem do blog é pouco sobre o tema e, na maioria das vezes, ofereceu 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 (links em inglês). Este artigo foi revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler e Rachel Andrew.