Skip to content

C# module build pipeline depends on deprecated .NET workload; AOT compilation path needed #4514

@cloutiertyler

Description

@cloutiertyler

Summary

C# modules in SpacetimeDB are compiled to WebAssembly using the .NET 8 Mono interpreter via the wasi-experimental workload. Microsoft has deprecated this workload and removed it from .NET 9+. There is no replacement. This means the entire C# module build pipeline has an expiration date: .NET 8 LTS support ends November 2026, and there is no way to build C# modules on any newer version of .NET using the current approach.

Separately, the Mono interpreter is slow, and there is no supported path to ahead-of-time (AOT) compilation for wasi-wasm targets that works across all platforms.

This issue documents the full situation, the options we've explored, and the path forward.

Current state

C# modules are built with dotnet publish targeting wasi-wasm using the wasi-experimental workload on .NET 8. This workload provides the Mono runtime compiled for wasi-wasm and the WASI SDK build tooling. Without it, dotnet publish with RuntimeIdentifier=wasi-wasm does not work at all.

The output is a single .wasm file that bundles the Mono runtime, which interprets C# IL bytecode at execution time. We don't actually use WASI. The wasi-wasm target is just a means to produce a .wasm file, and all WASI imports are stubbed out in bindings.c. Our modules use custom host imports/exports defined by the SpacetimeDB ABI.

Key files:

  • crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.props - MSBuild properties
  • crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets - MSBuild targets, WASI SDK download, AOT config
  • crates/bindings-csharp/Runtime/bindings.c - C glue layer for imports/exports and WASI shims
  • crates/bindings-csharp/Runtime/Internal/FFI.cs - C# FFI declarations
  • crates/cli/src/tasks/csharp.rs - CLI build orchestration

The existential problem

The wasi-experimental workload is the only way to build C# modules today. It was removed from .NET 9 (Feb 2025) and will not come back. Microsoft has no WASI plans for .NET 10 or 11. The WASI tracking issue milestone was moved to "Future" with no committed timeline.

.NET 8 LTS support ends November 2026. After that, there is no supported .NET version that can build C# modules using the current approach.

Therefore we need to move off of wasi-experiemental to some other solution that supports WASI or ideally raw Wasm. The best option for that is NativeAOT-LLVM (which currently doesn't build on macOS).

Why AOT also matters

Even while the interpreter path works on .NET 8, the Mono interpreter adds significant overhead. C# module code is interpreted at runtime inside WebAssembly rather than compiled to native wasm instructions. AOT compilation would compile C# directly to WebAssembly, eliminating the interpreter overhead.

Options we explored

1. Mono AOT via RunAOTCompilation=true (does not work)

The mainline .NET Mono AOT compiler (RunAOTCompilation) is used by Blazor for browser-wasm targets. We tested adding <RunAOTCompilation>true</RunAOTCompilation> to the build:

  • The wasm-tools workload only installs Cross.browser-wasm, not Cross.wasi-wasm
  • There is no Mono AOT cross-compiler for wasi-wasm targets
  • The flag is silently ignored; output is byte-identical with or without it
  • Confirmed by checking diagnostic MSBuild output: RunAOTCompilation is set but no MonoAOTCompiler task is ever invoked

Verdict: dead end. Microsoft has never shipped a Mono AOT cross-compiler for WASI.

2. .NET 9 Mono AOT with hacks (~20% improvement, abandoned)

.NET 9 added Mono AOT support for WASI, but simultaneously switched the output format to the Wasm Component Model (PR dotnet/runtime#104683). SpacetimeDB's Wasmtime host expects raw MVP wasm modules, not components.

@RReverser (Ingvar Stepanyan) tried working around this by:

Result: ~20% performance improvement over .NET 8 interpreter, but too many fragile hacks. Decided not to proceed. See this comment.

Verdict: works but too fragile for production.

3. .NET 10/11 WASI support (dropped by Microsoft)

  • The wasi-experimental workload was removed from .NET 9 (Feb 2025)
  • Not added back in .NET 10
  • WASI tracking issue milestone moved from .NET 10.0.0 to "Future"
  • Microsoft's .NET wasm team is focused on CoreCLR-for-browser-wasm, not WASI
  • Pavel Savara (Microsoft): "We are busy working on CoreCLR flavor of the WASM runtime, so no WASI plans for .NET 11. For now, NativeAOT-LLVM branch and community is still best choice for WASI." (comment)

Verdict: no help coming from mainline .NET.

4. NativeAOT-LLVM (best option, platform-limited)

NativeAOT-LLVM is an experimental fork of .NET's Native AOT toolchain that uses LLVM to compile C# directly to native WebAssembly. No Mono runtime is bundled. This is what Microsoft recommends for WASI/raw-wasm use cases.

Critically, NativeAOT-LLVM does not depend on the wasi-experimental workload. It uses its own compiler (Microsoft.DotNet.ILCompiler.LLVM) with MSBuildEnableWorkloadResolver=false. This makes it the escape hatch for both problems: it solves performance (AOT vs interpreter) and avoids the deprecated workload entirely.

RReverser already wired up experimental support in SpacetimeDB (PR #713, April 2024), activated via EXPERIMENTAL_WASM_AOT=1 environment variable.

Limitations:

  • The compiler only runs on Windows x64 and Linux x64. No macOS support
  • Depends on experimental NuGet packages from dotnet-experimental feed
  • The ABI versions in our infrastructure are stale (stuck at spacetime_10.0, current is up to 10.4)
  • Several host function imports are missing from the AOT path

Verdict: best path forward. Solves both the performance problem and the workload deprecation problem. We should update and support it for Windows/Linux users.

Path forward

  1. Update the existing NativeAOT-LLVM infrastructure to current ABI versions and add missing imports
  2. Keep the Mono interpreter as the default for all platforms (works on .NET 8 until Nov 2026)
  3. Document how to enable AOT for users on Windows x64 and Linux x64 who need better performance
  4. Add CI testing for AOT builds on Linux
  5. Track NativeAOT-LLVM for macOS support (no timeline from Microsoft)
  6. Long term, NativeAOT-LLVM may become the only way to build C# modules once .NET 8 reaches end of life

Relevant upstream issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions