As otimizações de chamada de cauda WasmGC e Wasm estão disponíveis como padrão

Publicado em 29 de janeiro de 2025

Coleta de lixo do WebAssembly (WasmGC)

Há dois tipos de linguagem de programação: com coleta de lixo e que exigem gerenciamento manual de memória. Kotlin, PHP ou 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.

Em termos simplificados, a 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 das mais fáceis de entender é a contagem de referências, em que o objetivo é contar o número de referências a objetos na memória.

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. Se os desenvolvedores quiserem compilar uma linguagem como PHP para Wasm, geralmente precisam compilar todas as partes, como o analisador da linguagem, o suporte à biblioteca, a coleta de lixo e outros componentes essenciais.

O Wasm é executado no navegador no contexto do JavaScript da linguagem host. No Chrome, o JavaScript e o Wasm são executados no V8, o mecanismo JavaScript de código aberto do Google. 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 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 o WasmGC entra.

Para saber mais sobre a WasmGC, leia A WebAssembly Garbage Collection (WasmGC) agora está ativada por padrão no Chrome. Se você quiser se aprofundar, confira a postagem do blog V8 A new way to bring garbage collected programming languages efficiently to WebAssembly.

Otimizações de chamada de cauda do Wasm

Uma chamada está na posição de cauda se for a última instrução executada antes do retorno da função atual. Os compiladores podem otimizar essas chamadas descartando o frame do autor da chamada e substituindo a chamada por um salto. Isso é especialmente útil para funções recursivas. Por exemplo, considere esta função C que soma os elementos de uma lista vinculada:

int sum(List* list, int acc) {
  if (list == nullptr) return acc;
  return sum(list->next, acc + list->val);
}

Com uma chamada regular, isso consome espaço de pilha O(n): cada elemento da lista adiciona um novo frame à pilha de chamadas. Com uma lista longa o suficiente, isso pode rapidamente transbordar a pilha. Ao substituir a chamada por um salto, a otimização de chamada de cauda transforma essa função recursiva em um loop que usa o espaço de pilha O(1):

int sum(List* list, int acc) {
  while (list != nullptr) {
    acc = acc + list->val;
    list = list->next;
  }
  return acc;
}

Essa otimização é particularmente importante para linguagens funcionais. Eles dependem muito de funções recursivas, e funções puras, como Haskell, nem mesmo fornecem estruturas de controle de loop. Qualquer tipo de iteração personalizada normalmente usa recursão de uma forma ou de outra. Sem a otimização de chamada de cauda, isso causaria um overflow de pilha muito rápido para qualquer programa não trivial, que de outra forma ficaria sem espaço de pilha.

Inicialmente, o WebAssembly não permitia essas otimizações de chamada de cauda, mas isso mudou com a proposta de extensão de chamada de cauda. Para saber mais, leia o artigo WebAssembly tail calls no blog V8.

Conclusões

Agora que as otimizações de WasmGC e de chamada de cauda estão disponíveis como padrão, mais apps podem usar esses recursos para melhorar o desempenho. Por exemplo, as Planilhas Google transferiram o worker de cálculo para o WasmGC.