Skip to content

fsharp-wasm/fsharp-wasm

fsharp-wasm

Compile F# to WebAssembly GC — natively, without JavaScript.

License: MIT CI .NET WasmGC

A standalone WasmGC backend for the Fable F# compiler. Produces .wasm binaries that run on any WasmGC-capable runtime — browsers, Node.js, Wasmtime, WasmEdge.


What is this?

fsharp-wasm compiles F# source code directly to WebAssembly with garbage collection (WasmGC). No JavaScript glue code, no Emscripten, no linear memory GC — just native Wasm structs and arrays managed by the host runtime's GC.

// hello.fs
module Hello

let fibonacci n =
    let mutable a, b = 0, 1
    for _ in 1..n do
        let t = a + b
        a <- b
        b <- t
    a

let isPrime n =
    if n < 2 then false
    else
        let mutable i = 2
        let mutable result = true
        while i * i <= n && result do
            if n % i = 0 then result <- false
            i <- i + 1
        result

Compile and run:

dotnet fable hello.fsproj --lang wasmgc
# → output/hello.wasm + output/hello.wat

Features

Implemented

Category Features
Core Language Let bindings, recursion, tail calls (return_call), mutual recursion
Type System Records, discriminated unions, pattern matching, generics (monomorphized)
Functions Closures, higher-order functions, currying, partial application
Collections List<'T>, Array<'T>, Option<'T>, Result<'T,'E> with full combinators
Strings Native UTF-16 on GC heap, 35+ operations (concat, split, join, format, trim, etc.)
Math Full System.Math (sin, cos, sqrt, abs, pow, min, max, etc.)
Formatting sprintf, printfn, $"interpolation"
Parsing Int32.Parse, Double.Parse, float cast
Multi-file Multiple .fs files compiled into a single Wasm module
FFI [<Import("name","module")>] for importing host/Wasm functions
Component Model WIT file generation, wasm-tools component embed/new integration
Module Linking Multiple F# compilation units composed at runtime
Binary Output Direct .wasm binary encoding (LEB128, all sections) + human-readable .wat

Not Yet Implemented

Feature Status
Generic Map<'K,'V> / Set<'T> Integer keys only; generic dispatch planned
Interface dispatch / vtables Design complete, implementation upcoming
seq { } / IEnumerable Planned
Typed exceptions Basic try/with works; typed throws planned
Async / JSPI Planned

Quick Start

Prerequisites

1. Clone with submodule

git clone --recursive https://github.com/fsharp-wasm/fsharp-wasm.git
cd fsharp-wasm

If you already cloned without --recursive:

git submodule update --init --recursive

2. Build

dotnet build src/Fable.WasmGc.fsproj

This builds:

  • The WasmGC backend (Fable.WasmGc.fsproj)
  • All required Fable projects from the vendored submodule (Fable.Compiler, Fable.Cli, etc.)

3. Run tests

# Unit tests (353 tests)
cd tests/QuickTest && bash run.sh

# Showcase (26 real-world algorithms)
cd tests/Showcase && bash run.sh

# Component model examples
cd examples/component-embed && bash run.sh    # 39 tests
cd examples/component-linking && bash run.sh  # 22 tests

Note: The test scripts run the compiler via dotnet run --project vendor/Fable/src/Fable.Cli which automatically resolves the WasmGC backend from the parent repo.

Releases

Pushing a v* tag runs .github/workflows/release.yml and publishes GitHub Release assets for:

  • the platform-specific Fable.Cli compiler archives
  • a Fable.WasmGc-net10.0.zip build of the backend DLLs
  • a Fable.WasmGc NuGet package (.nupkg)

NuGet upload is wired into the release workflow and stays skipped until the NUGET_API_KEY secret is configured.

Compile Your Own F# Project

  1. Create an F# project:

    dotnet new console -lang F# -n MyApp
  2. Compile to WasmGC:

    dotnet fable MyApp.fsproj --lang wasmgc
  3. Run with Node.js:

    const { readFileSync } = await import("fs");
    const bytes = readFileSync("output/MyApp.wasm");
    const { instance } = await WebAssembly.instantiate(bytes);
    console.log(instance.exports.myFunction(42));

Architecture

F# Source (.fs)
    │
    ▼
┌──────────────────────┐
│  F# Compiler Service │  Parse + type-check
│  (via Fable 5)       │
└──────────┬───────────┘
           │  Fable AST
           ▼
┌──────────────────────┐
│  Fable2WasmGc        │  F# AST → WasmGC IR
│  + Replacements      │  BCL inlining (List, Array, Option, Math, String...)
│  + Monomorphization  │  Generic specialization
└──────────┬───────────┘
           │  WExpr / WModule
           ▼
┌──────────────────────┐
│  Optimize            │  Constant folding, dead code elimination
└──────────┬───────────┘
           │
     ┌─────┴─────┐
     ▼           ▼
┌─────────┐ ┌──────────┐
│ WAT     │ │ Encoder  │  .wat (text)  +  .wasm (binary)
│ Emitter │ │ (Binary) │
└─────────┘ └──────────┘

See docs/architecture.md for details.

Project Structure

src/
├── WasmGc.AST.fs                 # WasmGC IR types (WType, WExpr, WConst)
├── WasmGcPipeline.fs             # Pipeline entry point + WIT generation
├── Runtime/                      # Runtime helpers, CE builder, type system
├── Transforms/                   # Fable AST → WasmGC IR translation
└── Emit/                         # WAT emitter, binary encoder, optimizer

fable-library-wasmgc/             # Minimal F# standard library for WasmGC
tests/                            # QuickTest (353) + Showcase (26) suites
examples/                         # Component model demos
docs/                             # Documentation
vendor/Fable/                     # Fable 5 with WasmGC patches

Documentation

How It Works

fsharp-wasm leverages Fable's frontend (F# Compiler Service + Fable's typed AST) and adds a completely new backend that targets WebAssembly GC:

  • Records → WasmGC struct types with named fields
  • Discriminated Unions → Struct hierarchy with tag dispatch via br_on_cast
  • Closures$AnyFn base struct with captured variables + call_ref dispatch
  • Strings → GC-managed (array i32) with 35+ runtime operations
  • Lists → Linked-list struct hierarchy ($ListBase / $ListCons)
  • Generics → Demand-driven monomorphization (specialized at each call site)
  • Tail calls → Native return_call (zero-cost, handles mutual recursion)

Sponsoring

fsharp-wasm is developed as an open-source project. If you find it useful or want to support the development of F# on WebAssembly, please consider sponsoring:

Sponsorship funds go directly toward:

  • Full-time development of the compiler backend
  • WasmGC specification compliance testing
  • Building a complete F# standard library for WebAssembly
  • Documentation and ecosystem tooling

License

MIT — Copyright (c) 2025–2026 Fable WasmGC Backend Contributors

Acknowledgments

About

A specialized F# to WebAssembly compiler leveraging WasmGC for high-performance, lightweight binaries.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors