Skip to content

Add .NET 10 support via NativeAOT-LLVM#4741

Draft
JasonAtClockwork wants to merge 21 commits intomasterfrom
jlarabie/nativeaot-net10
Draft

Add .NET 10 support via NativeAOT-LLVM#4741
JasonAtClockwork wants to merge 21 commits intomasterfrom
jlarabie/nativeaot-net10

Conversation

@JasonAtClockwork
Copy link
Copy Markdown
Contributor

Description of changes

  • Added a dual-path C# module workflow:
    • Stable/default path remains .NET 8
    • Experimental NativeAOT-LLVM path targets .NET 10 behind EXPERIMENTAL_WASM_AOT=1
  • Updated the C# runtime packages to switch targets conditionally for the experimental path:
    • SpacetimeDB.Runtime defaults to net8.0 and switches to net10.0 for NativeAOT
    • SpacetimeDB.BSATN.Runtime defaults to netstandard2.1;net8.0 and switches to netstandard2.1;net10.0
  • Added a .NET 8-safe shim for WasmImportLinkageAttribute and annotated the NativeAOT host imports that require it
  • Reduced the explicit experimental package set to the minimum required:
    • Microsoft.DotNet.ILCompiler.LLVM
    • runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM
  • Updated the regression-test C# projects to support both paths:
    • default net8.0
    • net10.0 when EXPERIMENTAL_WASM_AOT=1
  • Kept the required WASI overlay workaround for NativeAOT-LLVM core-module output:
    • strips .wit files from IlcFrameworkNativePath
    • preserves IlcSdkPath
  • Updated spacetime init --native-aot for C# so it injects the full known-good .NET 10 NativeAOT project shape into StdbModule.csproj
  • Updated spacetime publish --native-aot so the C# build path:
    • uses the .NET 10 NativeAOT output layout
    • passes the required MSBuild properties for wasm32-unknown-wasip1

Why the heavy changes?

NativeAOT-LLVM currently does not cleanly support the exact output shape we need for SpacetimeDB: a WASI Preview 1/core wasm module that can be hosted by SpacetimeDB/wasmtime today.

In practice, the LLVM/WASI toolchain wants to flow component-model metadata (*.wit) into the link step, which causes linker arguments like --component-type and breaks production of the core module we actually need. The important upstream issue is: [NativeAOT-LLVM] Output non component dotnet/runtimelab#3144

The current workaround is to overlay IlcFrameworkNativePath and exclude *.wit files before LinkNativeLlvm, while preserving IlcSdkPath. Through testing, this was the only project-level workaround that proved necessary; the earlier IlcLlvmTarget property and WasmComponentTypeWit Remove=... item removal were not required once the overlay was in place.

In order to strip out the *.wit files it was necessary to expand the module .csproj to include the following, which will copy out the outputs without the .wit files allowing the build to continue in a core module format:

  <Target Name="UseWasiRuntimeOverlayWithoutComponentWit"
          Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'"
          AfterTargets="SetupProperties"
          BeforeTargets="LinkNativeLlvm">
    <PropertyGroup>
      <_OriginalIlcSdkPath>$(IlcSdkPath)</_OriginalIlcSdkPath>
      <_WasiRuntimeOverlayDir>$(IntermediateOutputPath)native-hidden-no-wit\</_WasiRuntimeOverlayDir>
    </PropertyGroup>

    <ItemGroup>
      <_WasiRuntimeOverlaySource Include="$(IlcFrameworkNativePath)**\*" Exclude="$(IlcFrameworkNativePath)**\*.wit" />
    </ItemGroup>

    <RemoveDir Directories="$(_WasiRuntimeOverlayDir)" />
    <MakeDir Directories="$(_WasiRuntimeOverlayDir)" />
    <Copy SourceFiles="@(_WasiRuntimeOverlaySource)"
          DestinationFiles="@(_WasiRuntimeOverlaySource->'$(_WasiRuntimeOverlayDir)%(RecursiveDir)%(Filename)%(Extension)')" />

    <PropertyGroup>
      <IlcFrameworkNativePath>$(_WasiRuntimeOverlayDir)</IlcFrameworkNativePath>
      <IlcSdkPath>$(_OriginalIlcSdkPath)</IlcSdkPath>
    </PropertyGroup>
  </Target>

Why .NET 10 And Not .NET 9

I also investigated .NET 9 NativeAOT-LLVM as part of this work, but it is blocked by an upstream MemoryMarshal type-load failure in the NativeAOT-LLVM toolchain:

That issue reproduced in our C# module runtime path as well, so this PR keeps the stable path on .NET 8 and targets the experimental NativeAOT path on .NET 10.

API and ABI breaking changes

  • Currently breaks .NET 8 NativeAOT-LLVM support by updating references to .NET 10, it might be possible to target both but we'd need more control in the .csproj to disable the .NET 10 specific requirements.

Expected complexity level and risk

2 - The biggest risk is not the stable .NET 8 path, but future changes in the upstream NativeAOT-LLVM/WASI pipeline. This PR intentionally keeps the experimental logic explicit in the generated module project rather than hiding it in transitive package magic, because that proved more reliable during restore/publish testing. We also discussed if we run into troubles due to future changes from the NativeAOT-LLVM branch to pin our version which can be done through the PackageReferences.

Testing

  • Ran the C# regression tests on the stable .NET 8 path
  • Ran the C# regression tests on the experimental .NET 10 NativeAOT path
  • Verified spacetime init --lang csharp --native-aot generates a working StdbModule.csproj
  • Verified spacetime publish --native-aot successfully builds and publishes a generated C# project
  • Validated the NativeAOT path on both Windows and Linux

cloutiertyler and others added 21 commits March 1, 2026 22:20
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.
#4601)

# 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 <jason@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
@JasonAtClockwork JasonAtClockwork self-assigned this Apr 2, 2026
@JasonAtClockwork JasonAtClockwork requested a review from rekhoff April 2, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants