WasmGC and Wasm tail call optimizations are now Baseline Newly available

Published: January 29, 2025

WebAssembly Garbage Collection (WasmGC)

There are two types of programming languages: garbage-collected programming languages and programming languages that require manual memory management. Examples of the former, among many more, are Kotlin, PHP, or Java. Examples of the latter are C, C++, or Rust. As a general rule, higher-level programming languages are more likely to have garbage collection as a standard feature.

In simplified terms, garbage collection is the attempt to reclaim memory which was allocated by the program, but that is no longer referenced. Such memory is called garbage. There are many strategies for implementing garbage collection. One of the easiest to understand is reference counting where the objective is to count the number of references to objects in memory.

It may feel like inception, but programming languages are implemented in other programming languages. For example, the PHP runtime is primarily implemented in C. If developers want to compile a language like PHP to Wasm, they commonly need to compile all the parts, like the language's parser, library support, garbage collection, and other crucial components.

Wasm runs in the browser in the context of the host language JavaScript. In Chrome, JavaScript and Wasm are run in V8, Google's open source JavaScript engine. And, V8 already has a garbage collector. This means developers making use of, for example, PHP compiled to Wasm, end up shipping a garbage collector implementation of the ported language (PHP) to the browser that already has a garbage collector, which is as wasteful as it sounds. This is where WasmGC comes in.

To learn more about WasmGC, read WebAssembly Garbage Collection (WasmGC) now enabled by default in Chrome and if you want to go really deep, check out the V8 blog post A new way to bring garbage collected programming languages efficiently to WebAssembly

Wasm tail call optimizations

A call is said to be in tail position if it's the last instruction executed before returning from the current function. Compilers can optimize such calls by discarding the caller frame and replacing the call with a jump. This is especially useful for recursive functions. For example, take this C function that sums the elements of a linked list:

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

With a regular call, this consumes O(n) stack space: each element of the list adds a new frame on the call stack. With a long enough list, this could very quickly overflow the stack. By replacing the call with a jump, tail call optimization effectively turns this recursive function into a loop which uses O(1) stack space:

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

This optimization is particularly important for functional languages. They rely heavily on recursive functions, and pure ones like Haskell don't even provide loop control structures. Any kind of custom iteration typically uses recursion one way or another. Without tail call optimization, this would very quickly run into a stack overflow for any non-trivial program, which would otherwise quickly run out of stack space.

Initially, WebAssembly didn't allow for such tail call optimizations, but this changed with the Tail Call Extension proposal. To go deeper, read the article WebAssembly tail calls on the V8 blog.

Conclusions

Now with WasmGC and tail call optimizations being Baseline Newly available, more apps can use these features for better performance, like, for example, Google Sheets did by porting the Google Sheets calculation worker to WasmGC.