From 8526de8748abb8458fa9f936fcb4c36e15d7a2d7 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Sun, 1 Mar 2026 22:20:58 -0500 Subject: [PATCH 01/16] Update NativeAOT-LLVM infrastructure to current ABI and add CI smoketest The experimental NativeAOT-LLVM build path (EXPERIMENTAL_WASM_AOT=1) was missing several host function imports added in ABI versions 10.0-10.4, and the compiler package reference was hardcoded to Windows x64 only. Changes to SpacetimeDB.Runtime.targets: - Add missing spacetime_10.0 imports: datastore_update_bsatn, identity - Add all spacetime_10.1 imports: bytes_source_remaining_length - Add all spacetime_10.2 imports: get_jwt - Add all spacetime_10.3 imports: procedure_start_mut_tx, procedure_commit_mut_tx, procedure_abort_mut_tx, procedure_http_request - Add all spacetime_10.4 imports: datastore_index_scan_point_bsatn, datastore_delete_by_index_scan_point_bsatn - Replace hardcoded runtime.win-x64 package reference with runtime.$(NETCoreSdkPortableRuntimeIdentifier) so the AOT compiler package resolves correctly on both Windows x64 and Linux x64 - Use explicit version strings instead of $(SpacetimeNamespace) variable Changes to ci.yml: - Add AOT build smoketest in csharp-testsuite job to verify the NativeAOT-LLVM build path works on Linux x64 See #4514 for full context on the C# AOT situation. --- .github/workflows/ci.yml | 6 ++ .../Runtime/build/SpacetimeDB.Runtime.targets | 57 ++++++++++++------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3eb7e9cada..c13ecb07362 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -995,6 +995,12 @@ jobs: exit 1 } + - name: Smoketest C# AOT build (NativeAOT-LLVM) + run: | + cd modules/sdk-test-cs + EXPERIMENTAL_WASM_AOT=1 dotnet publish -c Release + test -f bin/Release/net8.0/wasi-wasm/publish/StdbModule.wasm + internal-tests: name: Internal Tests needs: [lints] diff --git a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets index 780d6f19f43..aa412113b31 100644 --- a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets +++ b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets @@ -4,27 +4,46 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + From 3e64dbd168f4ba623ba2828ba308532be9070878 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 13 Mar 2026 11:28:51 -0700 Subject: [PATCH 02/16] Update C# Runtime dependencies to support NativeAOT-LLVM prerequisites (#4601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes * Add dotnet-experimental feed + package source mapping for LLVM packages in `sdks/csharp/tools~/write-nuget-config.sh`, so generated NuGet.Config files include NativeAOT-LLVM prerequisites. * Make LLVM toolchain packages explicit dependencies in `SpacetimeDB.Runtime` to ensure restores succeed even when LLVM dependencies are only referenced through the `.nupkg`. * Import the LLVM targets from the package when `EXPERIMENTAL_WASM_AOT=1` to enable NativeAOT build steps without relying on downstream package reference resolution. # Context Changes are required to get `NativeAOT-LLVM` in #4515 to build correct, but moving the packages closer to the build, to ensure they get into the Nuget restore successfully. Additional changes where needed to `write-nuget-config.sh‎` to allow `Nuget.Config` files generated with required changes during regression testing. # API and ABI breaking changes None. # Expected complexity level and risk 2 (Low–moderate). Changes are scoped to build/restore infrastructure and package configuration. # Testing - [X] Built CLI locally - [X] Ran `run-regression-tests.sh` without errors --------- Co-authored-by: Jason Larabie --- .github/workflows/ci.yml | 36 ++++- crates/bindings-csharp/NATIVEAOT-LLVM.md | 124 ++++++++++++++++++ crates/bindings-csharp/Runtime/Runtime.csproj | 8 ++ .../Runtime/build/SpacetimeDB.Runtime.targets | 8 +- modules/sdk-test-cs/sdk-test-cs.csproj | 6 +- sdks/csharp/tools~/write-nuget-config.sh | 14 ++ 6 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 crates/bindings-csharp/NATIVEAOT-LLVM.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ae69a01cd3..e4007ba8422 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -869,11 +869,41 @@ jobs: exit 1 } + # NativeAOT-LLVM smoketest runs on Windows because runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM + # does not have 8.0.0 versions available on the dotnet-experimental NuGet feed. + csharp-aot-smoketest: + needs: [lints] + runs-on: windows-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - name: Install emscripten (Windows) + shell: pwsh + run: | + git clone https://github.com/emscripten-core/emsdk.git $env:USERPROFILE\emsdk + cd $env:USERPROFILE\emsdk + .\emsdk install 4.0.21 + .\emsdk activate 4.0.21 + - name: Smoketest C# AOT build (NativeAOT-LLVM) + shell: pwsh run: | - cd modules/sdk-test-cs - EXPERIMENTAL_WASM_AOT=1 dotnet publish -c Release - test -f bin/Release/net8.0/wasi-wasm/publish/StdbModule.wasm + $env:EXPERIMENTAL_WASM_AOT = "1" + if (Test-Path "$env:USERPROFILE\emsdk\emsdk_env.ps1") { + & "$env:USERPROFILE\emsdk\emsdk_env.ps1" | Out-Null + } + dotnet publish -c Release modules/sdk-test-cs + if (-not (Test-Path "modules/sdk-test-cs/bin/Release/net8.0/wasi-wasm/publish/StdbModule.wasm")) { + Write-Error "StdbModule.wasm not found" + exit 1 + } internal-tests: name: Internal Tests diff --git a/crates/bindings-csharp/NATIVEAOT-LLVM.md b/crates/bindings-csharp/NATIVEAOT-LLVM.md new file mode 100644 index 00000000000..362289af8b4 --- /dev/null +++ b/crates/bindings-csharp/NATIVEAOT-LLVM.md @@ -0,0 +1,124 @@ +# Converting a SpacetimeDB 2.0.x project to use NativeAOT-LLVM + +This guide provides instructions on taking an existing C# module that targets the public-released SpacetimeDB CLI, and guides you through the necessary steps to enable `NativeAOT-LLVM` use. + +## Overview +In order to use `NativeAOT-LLVM` on a C# module, we'll need to set the `EXPERIMENTAL_WASM_AOT` environment variable to `1` which SpacetimeDB will check during publishing of a module. +For the module to work, we'll also need the `NuGet.Config` and `.csproj` files with the required package sources and references. + +### Prerequisites: +- **.NET SDK 8.x** (same version used by SpacetimeDB) +- **Emscripten SDK (EMSDK)** installed (must contain `upstream/emscripten/emcc.bat`) +- **(Optional) Binaryen (wasm-opt)** installed and on `PATH` (recommended: `version_116`) + +## Steps + +1. **Install EMSDK** + - Download and extract the `https://github.com/emscripten-core/emsdk` release. + - Example path: `D:\Tools\emsdk` + +2. **Set environment variables** + + ```powershell + $env:EXPERIMENTAL_WASM_AOT=1 + $env:EMSDK="D:\Tools\emsdk" + ``` + +3. **Ensure NuGet feed is configured** + NativeAOT-LLVM packages currently come from **dotnet-experimental**: + - Add the `dotnet-experimental` feed to a project-local `NuGet.Config` + ```xml + + ``` + This should be a `NuGet.Config` placed in the root directory of your module folder (next to the `.csproj`). You can simply add the above line to the `packageSources` of your existing file, or if you need to create a minimal one, you can use: + + ```xml + + + + + + + + + ``` + +4. **Ensure NativeAOT emits a `.wasm` output** + - For LLVM AOT builds, the CLI currently accepts `dotnet.wasm` under `bin/Release/net8.0/wasi-wasm/publish/`. + - In the module `.csproj`, ensure the AOT package references include: + + ```xml + + + + + + ``` + + The contents of your `.csproj` should look something like this: + + ```xml + + + net8.0 + wasi-wasm + enable + enable + + + + + + + + + + + ``` + + - **NU1504 warning**: Because the runtime targets also add these LLVM packages, you may see a duplicate PackageReference warning. It is non-blocking. + +5. **(Optional) Install wasm-opt (Binaryen)** + This step is optional, but provides performance improvements, and therefore is recommended. + - Download Binaryen `https://github.com/WebAssembly/binaryen/releases/tag/version_116` for Windows and extract it, e.g. `D:\Tools\binaryen`. + - Add `D:\Tools\binaryen\bin` to `PATH`. + - Verify: + + ```powershell + wasm-opt --version + ``` + +6. **Publish module** + - Use the SpacetimeDB CLI to publish from the module directory. + - With `EXPERIMENTAL_WASM_AOT=1`, publish should attempt LLVM AOT. + - Ensure the local server is running if publishing to `local`. + +## Troubleshooting + +### Package source mapping enabled +If you have **package source mapping** enabled in `NuGet.Config`, you must add mappings for the LLVM packages or restores will fail. +Place the following in `NuGet.Config` inside the `configuration` section: +```xml + + + + + + + + + + + + + + + +``` + +### wasi-experimental workload install fails +If the CLI cannot install the `wasi-experimental` workload automatically, install it manually: + +```powershell +dotnet workload install wasi-experimental +``` diff --git a/crates/bindings-csharp/Runtime/Runtime.csproj b/crates/bindings-csharp/Runtime/Runtime.csproj index 1e5e0f8531a..12db5ab4150 100644 --- a/crates/bindings-csharp/Runtime/Runtime.csproj +++ b/crates/bindings-csharp/Runtime/Runtime.csproj @@ -12,6 +12,7 @@ true SpacetimeDB true + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json;$(RestoreAdditionalProjectSources) @@ -25,6 +26,13 @@ + + + + + + + diff --git a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets index aa412113b31..5f183e0e040 100644 --- a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets +++ b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets @@ -1,5 +1,9 @@ + + @@ -42,10 +46,6 @@ - - - - diff --git a/modules/sdk-test-cs/sdk-test-cs.csproj b/modules/sdk-test-cs/sdk-test-cs.csproj index d4caede5ffa..09cf1192bb7 100644 --- a/modules/sdk-test-cs/sdk-test-cs.csproj +++ b/modules/sdk-test-cs/sdk-test-cs.csproj @@ -9,5 +9,9 @@ - + + + + + diff --git a/sdks/csharp/tools~/write-nuget-config.sh b/sdks/csharp/tools~/write-nuget-config.sh index 9997cd219ff..69286284ad8 100755 --- a/sdks/csharp/tools~/write-nuget-config.sh +++ b/sdks/csharp/tools~/write-nuget-config.sh @@ -15,6 +15,8 @@ cat >NuGet.Config < + + @@ -30,6 +32,11 @@ cat >NuGet.Config < + + + + + @@ -43,6 +50,8 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" < + + @@ -58,6 +67,11 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" < + + + + + From faf7167b0b76af73bbbfda130c29f7f409feb98e Mon Sep 17 00:00:00 2001 From: rekhoff Date: Thu, 19 Mar 2026 08:50:51 -0700 Subject: [PATCH 03/16] Add `--native-aot` flag to publishing and init populating --- crates/cli/src/subcommands/init.rs | 109 +++++++++++++++++++++++++- crates/cli/src/subcommands/publish.rs | 18 +++++ 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/subcommands/init.rs b/crates/cli/src/subcommands/init.rs index b4701b41164..eb4939c4be8 100644 --- a/crates/cli/src/subcommands/init.rs +++ b/crates/cli/src/subcommands/init.rs @@ -116,6 +116,7 @@ pub struct TemplateConfig { pub github_repo: Option, pub template_def: Option, pub use_local: bool, + pub native_aot: bool, } #[derive(Debug, Clone, Default)] @@ -131,6 +132,8 @@ pub struct InitOptions { pub non_interactive: bool, /// When true, suppress the "Next steps" message after init (e.g. when called from `spacetime dev`). pub skip_next_steps: bool, + /// When true, configure C# projects for NativeAOT-LLVM compilation. + pub native_aot: bool, } impl InitOptions { @@ -146,6 +149,7 @@ impl InitOptions { local: args.get_flag("local"), non_interactive: args.get_flag("non-interactive"), skip_next_steps: false, + native_aot: args.get_flag("native-aot"), } } } @@ -189,6 +193,12 @@ pub fn cli() -> clap::Command { .action(clap::ArgAction::SetTrue) .help("Run in non-interactive mode"), ) + .arg( + Arg::new("native-aot") + .long("native-aot") + .action(clap::ArgAction::SetTrue) + .help("Configure C# project for NativeAOT-LLVM compilation (experimental, Windows only)"), + ) } pub async fn fetch_templates_list() -> anyhow::Result> { @@ -346,6 +356,7 @@ fn create_template_config_from_template_str( github_repo: None, template_def: Some(template.clone()), use_local: true, + native_aot: false, }) } else { // GitHub template @@ -358,6 +369,7 @@ fn create_template_config_from_template_str( github_repo: Some(template_str.to_string()), template_def: None, use_local: true, + native_aot: false, }) } } @@ -525,7 +537,13 @@ pub async fn exec_with_options(config: &mut Config, options: &InitOptions) -> an )?; init_from_template(&template_config, &template_config.project_path, is_server_only).await?; - if let Some(path) = create_default_spacetime_config_if_missing(&project_path)? { + // Add NativeAOT-LLVM package references to C# projects if --native-aot was specified + if options.native_aot && template_config.server_lang == Some(ServerLanguage::Csharp) { + let server_dir = template_config.project_path.join("spacetimedb"); + add_native_aot_packages_to_csproj(&server_dir)?; + } + + if let Some(path) = create_default_spacetime_config_if_missing(&project_path, options.native_aot)? { println!("{} Created {}", "✓".green(), path.display()); } @@ -605,7 +623,10 @@ fn get_local_database_name(options: &InitOptions, project_name: &str, is_interac Ok(database_name) } -fn create_default_spacetime_config_if_missing(project_path: &Path) -> anyhow::Result> { +fn create_default_spacetime_config_if_missing( + project_path: &Path, + native_aot: bool, +) -> anyhow::Result> { let config_path = project_path.join(CONFIG_FILENAME); if config_path.exists() { return Ok(None); @@ -622,6 +643,12 @@ fn create_default_spacetime_config_if_missing(project_path: &Path) -> anyhow::Re .insert("module-path".to_string(), json!("./spacetimedb")); } + if native_aot { + config + .additional_fields + .insert("native-aot".to_string(), json!(true)); + } + Ok(Some(config.save_to_dir(project_path)?)) } @@ -696,6 +723,7 @@ async fn get_template_config_non_interactive( github_repo: None, template_def: None, use_local: true, + native_aot: false, }) } @@ -761,6 +789,7 @@ async fn get_template_config_interactive( github_repo: None, template_def: None, use_local: true, + native_aot: false, }); } @@ -845,6 +874,7 @@ async fn get_template_config_interactive( github_repo: None, template_def: Some(template.clone()), use_local: true, + native_aot: false, }); } else if client_selection == github_clone_index { return loop { @@ -889,6 +919,7 @@ async fn get_template_config_interactive( github_repo: None, template_def: None, use_local: true, + native_aot: false, }); } else { unreachable!("Invalid selection index"); @@ -1648,6 +1679,21 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> anyhow::Result anyhow::Result<()> { Ok(()) } +/// Adds NativeAOT-LLVM package references to an existing C# .csproj file. +/// This is called when `--native-aot` is specified during `spacetime init`. +fn add_native_aot_packages_to_csproj(project_path: &Path) -> anyhow::Result<()> { + let csproj_path = project_path.join("StdbModule.csproj"); + if !csproj_path.exists() { + anyhow::bail!( + "Could not find StdbModule.csproj at {}", + csproj_path.display() + ); + } + + let content = std::fs::read_to_string(&csproj_path)?; + + // The NativeAOT-LLVM ItemGroup to add + let native_aot_item_group = r#" + + + + + +"#; + + // Insert the ItemGroup before the closing tag + let new_content = if let Some(pos) = content.rfind("") { + let (before, after) = content.split_at(pos); + format!("{}{}{}", before.trim_end(), native_aot_item_group, after) + } else { + anyhow::bail!("Invalid .csproj file: missing tag"); + }; + + std::fs::write(&csproj_path, new_content)?; + println!( + "{} Added NativeAOT-LLVM package references to {}", + "✓".green(), + csproj_path.display() + ); + + Ok(()) +} + pub fn init_typescript_project(project_path: &Path) -> anyhow::Result<()> { let export_files = vec![ ( @@ -2015,7 +2101,7 @@ mod tests { let project_path = temp.path(); std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap(); - let created = create_default_spacetime_config_if_missing(project_path) + let created = create_default_spacetime_config_if_missing(project_path, false) .unwrap() .expect("expected config to be created"); assert_eq!(created, project_path.join("spacetime.json")); @@ -2028,6 +2114,23 @@ mod tests { parsed.get("module-path").and_then(|v| v.as_str()), Some("./spacetimedb") ); + assert!(parsed.get("native-aot").is_none()); + } + + #[test] + fn test_create_default_spacetime_config_with_native_aot() { + let temp = tempfile::TempDir::new().unwrap(); + let project_path = temp.path(); + std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap(); + + let created = create_default_spacetime_config_if_missing(project_path, true) + .unwrap() + .expect("expected config to be created"); + assert_eq!(created, project_path.join("spacetime.json")); + + let content = std::fs::read_to_string(&created).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&content).unwrap(); + assert_eq!(parsed.get("native-aot").and_then(|v| v.as_bool()), Some(true)); } #[test] diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 30faf5e6052..04ac2ac9c8d 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -33,6 +33,7 @@ pub fn build_publish_schema(command: &clap::Command) -> Result( let parent = parent_opt.as_deref(); let org_opt = command_config.get_one::("organization")?; let org = org_opt.as_deref(); + let native_aot = command_config.get_one::("native_aot")?.unwrap_or(false); // If the user didn't specify an identity and we didn't specify an anonymous identity, then // we want to use the default identity @@ -447,6 +455,16 @@ async fn execute_publish_configs<'a>( println!("(JS) Skipping build. Instead we are publishing {}", path.display()); (path.clone(), "Js") } else { + // Set EXPERIMENTAL_WASM_AOT environment variable if native_aot is enabled + // This is read by the C# build system (MSBuild) and by csharp.rs to determine output paths + if native_aot { + println!("Using NativeAOT-LLVM compilation (experimental)"); + // SAFETY: We are single-threaded at this point and no other code is reading + // this environment variable concurrently. + unsafe { + env::set_var("EXPERIMENTAL_WASM_AOT", "1"); + } + } build::exec_with_argstring( path_to_project .as_ref() From 57551535cc957243914b7d73738fc182476d15d8 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Thu, 19 Mar 2026 09:00:37 -0700 Subject: [PATCH 04/16] Update NativeAOT-LLVM instructions use `--native-aot` flag --- crates/bindings-csharp/NATIVEAOT-LLVM.md | 208 ++++++++++++++--------- 1 file changed, 127 insertions(+), 81 deletions(-) diff --git a/crates/bindings-csharp/NATIVEAOT-LLVM.md b/crates/bindings-csharp/NATIVEAOT-LLVM.md index 362289af8b4..14092d95683 100644 --- a/crates/bindings-csharp/NATIVEAOT-LLVM.md +++ b/crates/bindings-csharp/NATIVEAOT-LLVM.md @@ -1,103 +1,139 @@ -# Converting a SpacetimeDB 2.0.x project to use NativeAOT-LLVM +# Using NativeAOT-LLVM with SpacetimeDB C# Modules -This guide provides instructions on taking an existing C# module that targets the public-released SpacetimeDB CLI, and guides you through the necessary steps to enable `NativeAOT-LLVM` use. +This guide provides instructions for enabling NativeAOT-LLVM compilation for C# SpacetimeDB modules, which can provide performance improvements. ## Overview -In order to use `NativeAOT-LLVM` on a C# module, we'll need to set the `EXPERIMENTAL_WASM_AOT` environment variable to `1` which SpacetimeDB will check during publishing of a module. -For the module to work, we'll also need the `NuGet.Config` and `.csproj` files with the required package sources and references. -### Prerequisites: +NativeAOT-LLVM compiles C# modules to native WebAssembly (WASM) instead of using the Mono runtime. + +This is currently only supported for Windows server modules and is experimental. Please test your modules throughly. + +## Prerequisites + - **.NET SDK 8.x** (same version used by SpacetimeDB) - **Emscripten SDK (EMSDK)** installed (must contain `upstream/emscripten/emcc.bat`) - **(Optional) Binaryen (wasm-opt)** installed and on `PATH` (recommended: `version_116`) +- **Windows** - NativeAOT-LLVM is currently only supported for Windows server modules -## Steps +## Prerequisites Installation -1. **Install EMSDK** - - Download and extract the `https://github.com/emscripten-core/emsdk` release. - - Example path: `D:\Tools\emsdk` +### Install Emscripten SDK (EMSDK) + +The Emscripten SDK is required for NativeAOT-LLVM compilation: -2. **Set environment variables** +1. **Download and extract** the Emscripten SDK from `https://github.com/emscripten-core/emsdk` + - Example path: `D:\Tools\emsdk` +2. **Set environment variable** (optional - the CLI will detect it automatically): ```powershell - $env:EXPERIMENTAL_WASM_AOT=1 $env:EMSDK="D:\Tools\emsdk" ``` -3. **Ensure NuGet feed is configured** - NativeAOT-LLVM packages currently come from **dotnet-experimental**: - - Add the `dotnet-experimental` feed to a project-local `NuGet.Config` - ```xml - - ``` - This should be a `NuGet.Config` placed in the root directory of your module folder (next to the `.csproj`). You can simply add the above line to the `packageSources` of your existing file, or if you need to create a minimal one, you can use: - - ```xml - - - - - - - - - ``` - -4. **Ensure NativeAOT emits a `.wasm` output** - - For LLVM AOT builds, the CLI currently accepts `dotnet.wasm` under `bin/Release/net8.0/wasi-wasm/publish/`. - - In the module `.csproj`, ensure the AOT package references include: - - ```xml - - - - - - ``` - - The contents of your `.csproj` should look something like this: - - ```xml - - - net8.0 - wasi-wasm - enable - enable - - - - - - - - - - - ``` - - - **NU1504 warning**: Because the runtime targets also add these LLVM packages, you may see a duplicate PackageReference warning. It is non-blocking. - -5. **(Optional) Install wasm-opt (Binaryen)** - This step is optional, but provides performance improvements, and therefore is recommended. - - Download Binaryen `https://github.com/WebAssembly/binaryen/releases/tag/version_116` for Windows and extract it, e.g. `D:\Tools\binaryen`. - - Add `D:\Tools\binaryen\bin` to `PATH`. - - Verify: - - ```powershell - wasm-opt --version - ``` - -6. **Publish module** - - Use the SpacetimeDB CLI to publish from the module directory. - - With `EXPERIMENTAL_WASM_AOT=1`, publish should attempt LLVM AOT. - - Ensure the local server is running if publishing to `local`. +### Install Binaryen (Optional) + +Binaryen provides `wasm-opt` for WASM optimization (recommended for performance): + +1. Download Binaryen `https://github.com/WebAssembly/binaryen/releases/tag/version_116` for Windows +2. Extract to e.g. `D:\Tools\binaryen` +3. Add `D:\Tools\binaryen\bin` to `PATH` +4. Verify: + ```powershell + wasm-opt --version + ``` + +## Creating a New NativeAOT Project + +When creating a new C# project, use the `--native-aot` flag: + +```powershell +spacetime init --lang csharp --native-aot my-native-aot-project +``` + +This automatically: +- Creates a C# project with the required package references +- Generates a `spacetime.json` with `"native-aot": true` +- Configures the project for NativeAOT-LLVM compilation + +## Converting an Existing Project + +### Manual Configuration + +1. **Update spacetime.json** + Add `"native-aot": true` to your `spacetime.json`: + ```json + { + "module": "your-module-name", + "native-aot": true + } + ``` + + **Note:** Once `spacetime.json` has `"native-aot": true`, you can simply run `spacetime publish` without the `--native-aot` flag. The CLI will automatically detect the configuration and use NativeAOT compilation. + +2. **Ensure NuGet feed is configured** + NativeAOT-LLVM packages come from **dotnet-experimental**. Add to `NuGet.Config`: + ```xml + + + + + + + + + ``` + +3. **Add NativeAOT package references** + Add this `ItemGroup` to your `.csproj`: + ```xml + + + + + + ``` + + Your complete `.csproj` should look like: + ```xml + + + net8.0 + wasi-wasm + enable + enable + + + + + + + + + + + ``` + +## Publishing Your NativeAOT Module + +After completing either the **Creating a New NativeAOT Project** or **Converting an Existing Project** steps above, you can publish your module normally: + +```powershell +# From your project directory +spacetime publish your-database-name +``` + +If you have `"native-aot": true` in your `spacetime.json`, the CLI will automatically detect this and use NativeAOT compilation. Alternatively, you can use: + +```powershell +spacetime publish --native-aot your-database-name +``` + +The CLI will display "Using NativeAOT-LLVM compilation (experimental)" when NativeAOT is enabled. ## Troubleshooting ### Package source mapping enabled -If you have **package source mapping** enabled in `NuGet.Config`, you must add mappings for the LLVM packages or restores will fail. -Place the following in `NuGet.Config` inside the `configuration` section: +If you have **package source mapping** enabled in `NuGet.Config`, add mappings for the LLVM packages: + ```xml @@ -122,3 +158,13 @@ If the CLI cannot install the `wasi-experimental` workload automatically, instal ```powershell dotnet workload install wasi-experimental ``` + +### Duplicate PackageReference warning +You may see a `NU1504` warning about duplicate `PackageReference` items. This is expected and non-blocking. + +### Code generation failed +If you see errors like "Code generation failed for method", ensure: +1. You're using `SpacetimeDB.Runtime` version 2.0.4 or newer +2. All required package references are in your `.csproj` +3. The `dotnet-experimental` feed is configured in `NuGet.Config` + From e22f9709e63a57a8a5394f63730d5dad9e4e4619 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Thu, 19 Mar 2026 09:44:30 -0700 Subject: [PATCH 05/16] Update lints --- crates/cli/src/subcommands/init.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/cli/src/subcommands/init.rs b/crates/cli/src/subcommands/init.rs index eb4939c4be8..4049036bf2d 100644 --- a/crates/cli/src/subcommands/init.rs +++ b/crates/cli/src/subcommands/init.rs @@ -644,9 +644,7 @@ fn create_default_spacetime_config_if_missing( } if native_aot { - config - .additional_fields - .insert("native-aot".to_string(), json!(true)); + config.additional_fields.insert("native-aot".to_string(), json!(true)); } Ok(Some(config.save_to_dir(project_path)?)) @@ -1681,16 +1679,16 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> anyhow::Result anyhow::Result<()> { fn add_native_aot_packages_to_csproj(project_path: &Path) -> anyhow::Result<()> { let csproj_path = project_path.join("StdbModule.csproj"); if !csproj_path.exists() { - anyhow::bail!( - "Could not find StdbModule.csproj at {}", - csproj_path.display() - ); + anyhow::bail!("Could not find StdbModule.csproj at {}", csproj_path.display()); } let content = std::fs::read_to_string(&csproj_path)?; From d9367b853368d18a12b4863233c3be1fda654e4d Mon Sep 17 00:00:00 2001 From: rekhoff Date: Thu, 19 Mar 2026 11:54:56 -0700 Subject: [PATCH 06/16] Add `--native-aot` to CLI Reference docs --- .../00200-reference/00100-cli-reference/00100-cli-reference.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md b/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md index 903703e5473..2434d57a9d6 100644 --- a/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md +++ b/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md @@ -111,6 +111,7 @@ Run `spacetime help publish` for more detailed information. * `-y`, `--yes` — Run non-interactively wherever possible. This will answer "yes" to almost all prompts, but will sometimes answer "no" to preserve non-interactivity (e.g. when prompting whether to log in with spacetimedb.com). * `--no-config` — Ignore spacetime.json configuration * `--env ` — Environment name for config file layering (e.g., dev, staging) +* `--native-aot` — Use NativeAOT-LLVM compilation for C# modules (experimental, Windows only) @@ -414,6 +415,7 @@ Initializes a new spacetime project. * `-t`, `--template