crates/bindings-csharp/NATIVEAOT-LLVM.md
This guide provides instructions for enabling NativeAOT-LLVM compilation for C# SpacetimeDB modules, which can provide performance improvements by compiling C# directly to native WebAssembly (WASM) using the .NET NativeAOT-LLVM toolchain.
[!WARNING] NativeAOT-LLVM is experimental.
SpacetimeDB supports three build targets for C# modules:
| Build Target | .NET Version | Platforms | Description |
|---|---|---|---|
| JIT (Mono) | .NET 8.0 | Windows, Linux, macOS | Uses the Mono runtime interpreter (default) |
| NativeAOT-LLVM | .NET 8.0 | Windows only | Compiles C# to native WASM |
| NativeAOT-LLVM | .NET 10.0+ | Windows, Linux | Compiles C# to native WASM |
[!NOTE] .NET 8.0 NativeAOT-LLVM is Windows-only because
runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVMwas never published to the dotnet-experimental feed.
The WASI SDK is required for NativeAOT-LLVM compilation and is automatically downloaded:
| Platform | Download Location |
|---|---|
| Windows | %USERPROFILE%\.wasi-sdk\wasi-sdk-29 |
| Linux/macOS | ~/.wasi-sdk/wasi-sdk-29 |
Override with the WASI_SDK_PATH environment variable:
# Windows
$env:WASI_SDK_PATH="C:\Tools\wasi-sdk"
# Linux/macOS
export WASI_SDK_PATH=/opt/wasi-sdk
For Windows users who want NativeAOT-LLVM compilation using .NET 8.0 SDK.
Your .csproj must include the conditional LLVM package references:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SpacetimeDB.Runtime" Version="2.2.*" />
</ItemGroup>
<!-- Required for .NET 8 AOT builds -->
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
</Project>
Your NuGet.Config must include:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="dotnet-experimental">
<package pattern="Microsoft.DotNet.ILCompiler.LLVM" />
<package pattern="runtime.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
There are three ways to enable NativeAOT-LLVM for .NET 8 builds.
Option 1: --native-aot flag during init
spacetime init --lang csharp --native-aot --dotnet-version 8 my-project
Option 2: --native-aot flag during publish
spacetime publish --native-aot my-database-name
Option 3: spacetime.json configuration
{
"module": "my-module",
"native-aot": true
}
Technically all of these options just set the EXPERIMENTAL_WASM_AOT environment variable, but they provide different user experiences. Using --native-aot during init will create a project with a spacetime.json configured like Option 3 so the new project is consistently published with NativeAOT-LLVM.
dotnet build with NativeAOT-LLVMTo use NativeAOT-LLVM, the project expects the flag EXPERIMENTAL_WASM_AOT to be set. When calling spacetime publish, this is dealt with internally. To build your project manually calling dotnet build, if you want to use NativeAOT-LLVM, you need to set the flag manually: dotnet build -f net8.0 -p:EXPERIMENTAL_WASM_AOT=1.
For users who want NativeAOT-LLVM compilation on Windows or Linux.
For .NET 10, the project configuration is simpler - no conditional package references needed:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SpacetimeDB.Runtime" Version="2.2.*" />
</ItemGroup>
</Project>
Your NuGet.Config must include:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="dotnet-experimental">
<package pattern="Microsoft.DotNet.ILCompiler.LLVM" />
<package pattern="runtime.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
If .NET 10 is not your default SDK, create a global.json:
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestMinor"
}
}
This is automatically created by the CLI when using the init command with --dotnet-version 10.
NativeAOT-LLVM is automatically used when targeting .NET 10. You can also explicitly enable it:
Option 1: Target .NET 10 during init (recommended)
spacetime init --lang csharp --dotnet-version 10 my-project
Option 2: Use --native-aot flag
spacetime init --lang csharp --native-aot my-project
Option 3: spacetime.json configuration
{
"module": "my-module",
"native-aot": true
}
Once configured, publish normally:
spacetime publish my-database-name
The CLI will display which build path is being used:
To explicitly publish with a specific .NET version:
# Force .NET 8 build (requires --native-aot for AOT)
spacetime publish --dotnet-version 8 --native-aot my-database-name
# Force .NET 10 build (automatically uses AOT)
spacetime publish --dotnet-version 10 my-database-name
Error:
error : Could not find wasi-sdk. Either set $(WASI_SDK_PATH), or use workloads to get the sdk.
Solution:
WASI_SDK_PATH environment variableError: Missing runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM
Cause: .NET 8 NativeAOT-LLVM packages were only published for Windows.
Solution: Use .NET 10 for Linux NativeAOT builds:
spacetime init --lang csharp --dotnet-version 10 my-project
Error: NativeAOT-LLVM fails with an unhelpful error such as:
EXEC : error : Object reference not set to an instance of an object.
...
Cause: The .NET 8 NativeAOT-LLVM toolchain can fail when a module defines a JsonSerializerContext that uses [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]. The latest .NET 8 NativeAOT-LLVM package is very old (October 2023) and contains a bug.
Solutions:
spacetime init --lang csharp --dotnet-version 10 my-project
--native-aot.PropertyNameCaseInsensitive = true from JsonSourceGenerationOptions and pass case-insensitive options at the call site:
var result = JsonSerializer.Deserialize<T>(
json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
The workaround can compile and run, but it's not fail proof. In trimmed AOT builds you'll see a IL2026 warning because this solution relies on reflection for code that may be getting trimmed. Treat it as a possible compatibility workaround rather than a guaranteed fix for every module.
For JIT builds only (not NativeAOT), you need the wasi-experimental workload:
dotnet workload install wasi-experimental
NativeAOT-LLVM builds do not use this workload; they use the WASI SDK instead.
If you see "Code generation failed for method" errors:
NuGet.Config includes the dotnet-experimental feedEXPERIMENTAL_WASM_AOT condition is in your .csprojTargetFramework is net10.0global.json exists if .NET 10 is not your default SDKThis warning is expected for .NET 8 AOT builds and is non-blocking.