WebAssembly GC Language Support: 5 Core Patterns for Kotlin & Dart Near-Native Browser Execution

边缘计算

Introduction

You're a Kotlin or Dart developer wanting to compile your application to WebAssembly for browser execution, only to discover — GC languages can't compile to Wasm. Traditional Wasm only supports linear memory with no garbage collection mechanism. Kotlin/Dart, being GC languages, must either rely on JS bridging (terrible performance) or bundle their own GC runtime (bloated 30MB+ modules). The experience is nothing short of disastrous.

The WebAssembly GC proposal changes everything. Wasm GC provides native garbage collection primitives for the VM: struct types, array types, and type references. GC languages can directly map to the Wasm GC type system without building their own GC runtime. In 2026, Kotlin/Wasm and Dart/Wasm have moved from experimental to production, achieving near-native execution performance in the browser.

This article dives deep into 5 core patterns, guiding you from zero to building Kotlin/Dart browser applications based on Wasm GC.

Core Concepts Reference

Concept Description Status
Wasm GC WebAssembly garbage collection proposal providing struct/array/GC types Stable
Struct Type GC-managed reference type, similar to OOP objects Stable
Array Type GC-managed variable-length array supporting reference elements Stable
GC Proposal Wasm GC Phase 1-3, progressively introducing GC primitives Phase 3
Kotlin/Wasm Kotlin backend compiling to Wasm GC, replacing Kotlin/JS Beta
Dart/Wasm Dart backend compiling to Wasm GC, replacing dart2js Stable
Type Reference externref/typeref for Wasm-host type interop Stable
Interop Bridging mechanism between Wasm GC objects and JavaScript objects Stable

Problem Analysis: 5 Major Challenges for Wasm GC Languages

1. GC Languages Can't Compile to Wasm

Traditional Wasm only has linear memory and basic value types — no heap, no references, no GC. Kotlin/Dart's object model cannot be directly mapped. Compilers must embed the entire GC runtime into the Wasm module, causing massive bloat and slow startup.

2. Immature Kotlin/Dart Wasm Ecosystem

Before 2024, both Kotlin/Wasm and Dart/Wasm were experimental — lacking IDE support, debugging tools, and third-party library compatibility. Production use was nearly impossible.

3. GC Performance Overhead

Even with native Wasm GC primitives, GC pause times, memory allocation frequency, and generational collection strategies still impact runtime performance, especially in animation-heavy and interaction-intensive scenarios.

4. Complex JS Interop

Wasm GC objects and JavaScript objects belong to different type systems. Cross-boundary passing requires wrapping/unwrapping, and type conversion and lifecycle management are error-prone.

5. Browser Compatibility

Wasm GC requires browser support for new instruction sets. Only Chrome 119+ and Firefox 120+ enable it by default. Safari support came later, and older browsers are completely incompatible.

Pattern 1: Kotlin/Wasm Project Configuration

Kotlin/Wasm supports the Wasm GC target through Kotlin Multiplatform projects:

// build.gradle.kts
kotlin {
    wasmJs {
        moduleName = "wasmApp"
        browser {
            commonWebpackConfig {
                outputFileName = "wasmApp.js"
            }
        }
        binaries.executable()
    }
    sourceSets {
        val wasmJsMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
            }
        }
    }
}

HTML Entry File:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Kotlin/Wasm App</title>
</head>
<body>
    <script src="wasmApp.js"></script>
</body>
</html>

Key Configuration Points:

  • The wasmJs {} block declares the Wasm GC target; the Kotlin compiler automatically generates GC types
  • binaries.executable() produces a directly runnable Wasm module
  • Dependencies must support the Wasm target; pure JVM libraries are unavailable

Pattern 2: Dart/Wasm Project Configuration

Dart 3.3+ natively supports the Wasm GC compilation target:

# pubspec.yaml
name: dart_wasm_app
description: Dart Wasm GC application
version: 1.0.0

environment:
  sdk: '>=3.3.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
# Compile to Wasm GC
dart compile wasm -O2 -o main.wasm bin/main.dart

# Flutter Web compile to Wasm
flutter build web --wasm

Dart Wasm Entry:

import 'dart:js_interop';

@JS()
extension type JSConsole._(JSObject _) implements JSObject {
  external static void log(JSString message);
}

void main() {
  final message = 'Hello from Dart/Wasm!'.toJS;
  JSConsole.log(message);
}

Key Configuration Points:

  • dart compile wasm directly generates a Wasm GC module without an additional runtime
  • dart:js_interop provides type-safe JS interop APIs
  • Flutter Web's --wasm flag enables the Wasm GC rendering backend

Pattern 3: GC Object and JS Interop

Interop between Wasm GC objects and JS objects is the core challenge. Kotlin and Dart provide different bridging mechanisms:

Kotlin/Wasm Interop:

import kotlinx.js.jsObject
import kotlin.js.JsAny

external interface JsUser : JsAny {
    var name: String
    var age: Int
}

fun createJsUser(): JsUser = jsObject {
    name = "Zhang"
    age = 30
}

fun processJsUser(user: JsUser): String {
    return "User: ${user.name}, Age: ${user.age}"
}

Dart/Wasm Interop:

import 'dart:js_interop';

@JS()
extension type JsUser._(JSObject _) implements JSObject {
  external String get name;
  external set name(String value);
  external int get age;
  external set age(int value);
}

JsUser createJsUser() {
  final user = JsUser._(JSObject());
  user.name = 'Zhang';
  user.age = 30;
  return user;
}

Interop Key Points:

  • Kotlin uses JsAny as the base type for JS objects and jsObject {} to create JS objects
  • Dart uses extension type + @JS() annotation for JS type bindings
  • Note basic type conversions at boundaries: Kotlin StringJsString, Dart StringJSString

Pattern 4: Performance Optimization and Memory Management

Wasm GC application performance optimization requires attention to GC pauses, memory allocation, and object lifecycle:

Kotlin/Wasm Performance Optimization:

// Avoid high-frequency GC: object pool reuse
class ObjectPool<T>(private val factory: () -> T) {
    private val pool = mutableListOf<T>()

    fun acquire(): T = pool.removeLastOrNull() ?: factory()

    fun release(obj: T) {
        pool.add(obj)
    }
}

data class Particle(var x: Float, var y: Float, var alive: Boolean)

fun simulate() {
    val pool = ObjectPool { Particle(0f, 0f, false) }
    val particles = mutableListOf<Particle>()

    repeat(1000) {
        val p = pool.acquire()
        p.x = it.toFloat()
        p.y = it.toFloat()
        p.alive = true
        particles.add(p)
    }

    particles.forEach { p ->
        p.alive = false
        pool.release(p)
    }
    particles.clear()
}

Dart/Wasm Performance Optimization:

// Use final and const to reduce GC pressure
class RenderConfig {
  final int width;
  final int height;
  final double scale;

  const RenderConfig({
    required this.width,
    required this.height,
    this.scale = 1.0,
  });
}

// Avoid frequent closure creation
typedef TransformOp = double Function(double);

double applyTransform(List<double> data, TransformOp op) {
  var result = 0.0;
  for (final value in data) {
    result += op(value);
  }
  return result;
}

void main() {
  final data = List.generate(10000, (i) => i.toDouble());
  final op = (double v) => v * 2.0; // Reuse closure
  applyTransform(data, op);
}

Performance Optimization Key Points:

  • Object pools reduce GC allocation frequency, ideal for animation/game scenarios
  • const/final constructors let the compiler optimize allocation strategies
  • Avoid creating temporary objects and closures in hot paths

Pattern 5: Production Deployment and Compatibility

Deploying Wasm GC applications requires handling browser compatibility and fallback strategies:

// Kotlin/Wasm: Detect browser support
fun isWasmGcSupported(): Boolean = js(
    "() => typeof WebAssembly !== 'undefined' && " +
    "WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0]))"
)

fun bootstrap() {
    if (isWasmGcSupported()) {
        println("Wasm GC supported, loading wasm module...")
        startWasmApp()
    } else {
        println("Wasm GC not supported, falling back to JS...")
        startJsApp()
    }
}

external fun startWasmApp()
external fun startJsApp()

Dart/Wasm Fallback Configuration:

import 'dart:js_interop';

bool isWasmGcSupported() {
  return _checkWasmGcSupport().toDart;
}

@JS('WebAssembly.validate')
external JSBoolean _checkWasmGcSupport(JSUint8Array bytes);

void main() {
  if (isWasmGcSupported()) {
    runApp(const WasmApp());
  } else {
    runApp(const JsFallbackApp());
  }
}

Nginx Deployment Configuration:

server {
    listen 443 ssl;
    server_name app.example.com;

    # Wasm MIME type
    types {
        application/wasm wasm;
    }

    location / {
        root /var/www/app;
        try_files $uri $uri/ /index.html;

        # Wasm caching strategy
        location ~* \.wasm$ {
            add_header Cache-Control "public, max-age=31536000, immutable";
        }
    }
}

Pitfall Guide: 5 Common Traps

1. ❌ Building custom GC in Wasm module → ✅ Use Wasm GC native types

Building a custom GC runtime causes 30MB+ module bloat and poor performance. Use Wasm GC struct and array types directly.

2. ❌ Ignoring JsAny/JSObject boundaries → ✅ Use type-safe interop APIs

Directly manipulating JS objects leads to type errors and memory leaks. Use JsAny in Kotlin and extension type in Dart.

3. ❌ Frequent object creation in hot paths → ✅ Object pools and const optimization

Creating objects every frame in animation loops triggers frequent GC pauses. Use object pool reuse or const constructors.

4. ❌ No browser compatibility detection → ✅ Detect and fallback at startup

Wasm GC requires Chrome 119+/Firefox 120+. Loading without detection causes white screens on older browsers.

5. ❌ Mixing Kotlin/JS and Kotlin/Wasm dependencies → ✅ Verify dependency Wasm target support

Not all Kotlin/JS libraries support the Wasm target. Incompatible dependencies cause compilation failures.

Error Troubleshooting: 10 Common Errors

Error Message Cause Solution
Uncaught LinkError: WebAssembly.instantiate() Browser doesn't support Wasm GC Check browser version, provide JS fallback
TypeError: struct.new requires gc types Wasm module uses GC instructions but runtime doesn't support them Upgrade browser or use polyfill
CompileError: Wasm GC not enabled Wasm GC feature not enabled Enable #enable-experimental-webassembly-features in Chrome
Uncaught RuntimeError: illegal cast JsAny type cast failure Check actual JS object type, use safeCast
OutOfMemoryError Excessive GC object allocation Reduce object creation, use object pools
LinkError: import alignment mismatch Wasm import type doesn't match host Check JS export function signatures
TypeError: Cannot read property of undefined JS interop accessing undefined property Use optional chaining ?.
Compile error: Unresolved reference JsAny Missing Wasm JS interop dependency Add kotlin-js-wasm dependency
dart2wasm: unsupported import Dart library doesn't support Wasm compilation Check if dependency supports Wasm target
GC pause > 16ms GC pause causing frame drops Optimize object allocation, reduce GC pressure

Advanced Optimization Tips

1. Generational GC Tuning

Wasm GC supports generational collection. Reduce old-generation scan frequency to lower pause times. In Kotlin/Dart, avoid cross-generational references to allow short-lived objects to be collected quickly.

2. Wasm GC and Web Workers

Move compute-intensive tasks to Web Workers to prevent GC pauses from blocking the main thread:

// Kotlin/Wasm Worker communication
fun startWorker() {
    val worker = js("new Worker('worker.js')")
    worker.postMessage(js("{ type: 'compute', data: [1,2,3] }"))
    worker.onmessage = { event ->
        val result = event.data.result
        println("Worker result: $result")
    }
}

3. AOT Compilation Optimization

Dart's AOT compiler performs tree-shaking and type specialization in Wasm GC mode, ensuring the final module only contains actually-used code paths.

4. Incremental GC Strategy

For large applications, use Incremental GC to spread GC work across multiple frames, avoiding single long pauses:

// Dart Wasm incremental GC hint
void frameCallback(Duration timestamp) {
  performIncrementalGc();
  renderFrame();
  SchedulerBinding.instance.scheduleFrameCallback(frameCallback);
}

5. Memory Profiling and Monitoring

Use Chrome DevTools' Memory panel to analyze Wasm GC memory allocation, identify GC hotspots and memory leaks.

Comparison: Kotlin/Wasm vs Dart/Wasm vs Blazor WASM vs TeaVM

Feature Kotlin/Wasm Dart/Wasm Blazor WASM TeaVM
Language Kotlin Dart C# Java
GC Mechanism Wasm GC native Wasm GC native Custom GC runtime Custom GC runtime
Module Size ~200KB ~150KB ~2MB ~500KB
Startup Speed Fast Fast Slow Medium
JS Interop JsAny API dart:js_interop JSInterop JSBody
Framework Support Compose Multiplatform Flutter Web Blazor None
Debugging Support IDE source maps DevTools IDE source maps Limited
Maturity Beta Stable Stable Experimental
Ecosystem Richness Medium Rich Rich Limited

The following tools can significantly boost your efficiency during Wasm GC language development:

  1. JSON Formatter — Format and validate Wasm module JSON configuration files, debug JS interop data
  2. Hash Encoding Tool — Generate integrity check hashes for Wasm modules, ensuring distribution security
  3. cURL to Code Converter — Convert API requests to Kotlin/Dart Wasm code, quickly integrate backend services

Conclusion and Outlook

WebAssembly GC language support in 2026 has evolved from "experimental" to "productive." Kotlin/Wasm and Dart/Wasm directly map to the Wasm GC type system, eliminating the bloat and performance overhead of custom GC runtimes, achieving near-native execution performance in the browser.

"Wasm GC isn't about making GC languages barely run in the browser — it's about making them run natively. When Kotlin and Dart no longer need JS bridging, the last language barrier in web development will be completely broken."

Directions worth watching: Wasm GC FinalizationRegistry, native Wasm GC support for more languages (Java/C#), and deep integration of Wasm GC with the Component Model.

Further Reading

  1. WebAssembly GC Proposal
  2. Kotlin/Wasm Official Documentation
  3. Dart Web (Wasm) Guide
  4. Wasm GC Browser Compatibility
  5. Flutter Web Wasm Migration Guide

Try these browser-local tools — no sign-up required →

#Wasm GC#垃圾回收#Kotlin Wasm#Dart Wasm#2026#边缘计算