-
Notifications
You must be signed in to change notification settings - Fork 812
Description
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 propertiescrates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets- MSBuild targets, WASI SDK download, AOT configcrates/bindings-csharp/Runtime/bindings.c- C glue layer for imports/exports and WASI shimscrates/bindings-csharp/Runtime/Internal/FFI.cs- C# FFI declarationscrates/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-toolsworkload only installsCross.browser-wasm, notCross.wasi-wasm - There is no Mono AOT cross-compiler for
wasi-wasmtargets - The flag is silently ignored; output is byte-identical with or without it
- Confirmed by checking diagnostic MSBuild output:
RunAOTCompilationis set but noMonoAOTCompilertask 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:
- Stripping component model linking with
-fuse-ld=lldand removing WIT file linker args - Working around trimmer stripping
UnmanagedCallersOnlyexports (dotnet/runtime#101434) - Working around exports with 9+ arguments crashing (dotnet/runtime#109338)
- Working around AOT issues (dotnet/runtime#101276)
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-experimentalworkload was removed from .NET 9 (Feb 2025) - Not added back in .NET 10
- WASI tracking issue milestone moved from
.NET 10.0.0to "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-experimentalfeed - The ABI versions in our infrastructure are stale (stuck at
spacetime_10.0, current is up to10.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
- Update the existing NativeAOT-LLVM infrastructure to current ABI versions and add missing imports
- Keep the Mono interpreter as the default for all platforms (works on .NET 8 until Nov 2026)
- Document how to enable AOT for users on Windows x64 and Linux x64 who need better performance
- Add CI testing for AOT builds on Linux
- Track NativeAOT-LLVM for macOS support (no timeline from Microsoft)
- Long term, NativeAOT-LLVM may become the only way to build C# modules once .NET 8 reaches end of life
Relevant upstream issues
- dotnet/runtime#65895 - WASI support tracking (where we posted our comment)
- dotnet/runtime#96419 - WASI developer experience goals
- dotnet/runtimelab feature/NativeAOT-LLVM - NativeAOT-LLVM branch
- dotnet/runtimelab#2378 - NativeAOT-LLVM WASI issues
- dotnet/runtime#104683 - .NET 9 switch to Component Model
- componentize-dotnet - Bytecode Alliance wrapper around NativeAOT-LLVM