Limitations of compiling GC languages to Wasm

WebAssembly (Wasm) was originally designed for low-level languages with manual memory management, such as C, C++, and Rust. As developers increasingly look to compile garbage-collected (GC) languages like Go, Java, C#, and Python to Wasm, they encounter unique technical bottlenecks. This article outlines the primary limitations of compiling GC languages to WebAssembly, highlighting the challenges of binary size, execution overhead, browser integration, and the current state of native Wasm garbage collection.

1. Bloated Binary Sizes

Traditionally, compiling a GC-dependent language to Wasm required compiling the language’s entire runtime—including its garbage collector—into the final .wasm binary. This “ship-the-runtime” approach leads to massive file sizes. A simple “Hello World” application in Go or C# can easily result in multi-megabyte files. In web environments, large binaries delay load times, increase bandwidth consumption, and degrade the user experience.

2. Memory Overhead and Performance Drag

Running a custom garbage collector inside the Wasm virtual machine introduces significant performance penalties. Because Wasm operates on a linear memory model, custom GCs must manage memory allocations manually within this sandbox. These collectors lack low-level access to the host operating system or CPU features, making them less efficient than native implementations. Additionally, having two garbage collectors running simultaneously—the browser’s JavaScript GC and the Wasm runtime’s GC—can lead to high CPU usage and memory fragmentation.

3. Integration Barriers with JavaScript and the DOM

Wasm modules cannot directly access the browser DOM or JavaScript APIs; they must communicate through import and export functions. For GC languages, passing complex objects back and forth requires serialized data or complex “glue code.” Managing references across the boundary is highly prone to memory leaks. If a JavaScript object references a Wasm-managed object, or vice versa, the respective garbage collectors cannot easily detect cyclic dependencies, often keeping unused memory allocated indefinitely.

4. Teething Pain with WasmGC Adoption

To address these issues, the WebAssembly community developed “WasmGC,” a specification that allows Wasm binaries to utilize the host browser’s native garbage collector. While WasmGC is now supported by major modern browsers (including Chrome, Firefox, and Safari), several limitations remain: * Toolchain Maturity: Compilers for languages like Kotlin, Java (via TeaVM/J2CL), and Dart are still adapting to WasmGC. It will take time before these toolchains generate code as optimized as their native counterparts. * Legacy Compatibility: Environments without native WasmGC support (such as older browsers or legacy Node.js runtimes) cannot run these binaries without slow polyfills. * Language Semantics: Many GC languages have unique memory models (e.g., weak references, finalizers, or pinning memory for interop) that do not map perfectly to the standardized WasmGC design.

5. Limited Multi-Threading Support

Many garbage-collected languages rely heavily on multi-threading for background tasks and UI responsiveness. While WebAssembly supports threading via Web Workers and SharedArrayBuffer, sharing garbage-collected objects across threads in Wasm is still highly restricted. The current WasmGC specification does not fully support thread-safe shared GC objects, making it difficult to run highly concurrent GC applications at native speeds in Wasm.