Epic: NativeAOT output target for SharpTS-compiled programs
- Dominant language
- C#
- Stars
- 154
- Forks
- 4
- Avg merge
- 2h 46m
- Merged PRs (30d)
- 189
Description
## Goal
Add a **true NativeAOT output target** for SharpTS-compiled TypeScript programs:
```text
TypeScript -> SharpTS IL compiler -> managed guest assembly -> .NET ILC -> native executable
```
This is deliberately separate from #1324. That epic NativeAOT-compiles the **SharpTS compiler/interpreter host**; its generated programs still run as managed IL under CoreCLR. This epic tracks NativeAOT-compiling the **generated program itself**, with no JIT at runtime.
## Feasibility result
A local win-x64 spike with .NET SDK 10.0.302 / ILC 10.0.10 proved that current SharpTS-generated IL is fundamentally consumable by NativeAOT:
- SharpTS compiled a TypeScript program with `--ref-asm`.
- A small `PublishAot=true` wrapper referenced the generated assembly.
- ILC produced a 3.9 MB native executable.
- The executable ran correctly across classes, array callbacks, objects, JSON, `Map`, `Set`, regex, and console output.
The spike also showed why this needs a compatibility project rather than only a packaging switch:
- ILC reported roughly three dozen trim/AOT warnings in the emitted runtime.
- The warnings cover reflection-based property/method/function dispatch, `Assembly.GetType`, stack inspection, and runtime generic closure.
- `SymbolClosedOwner(Type)` produced a real `IL3050` for `Type.MakeGenericType`.
- Rooting the entire generated assembly surfaced more problematic paths and is not a safe general solution.
- A C#-nameable generated entry-point facade is needed; `$Program` cannot be referenced directly from C#.
## Proposed product surface
Initial CLI shape (exact spelling to settle in the design task):
```bash
sharpts --compile app.ts --target native-aot --runtime win-x64
```
Also provide an inspectable/reproducible mode:
```bash
sharpts --compile app.ts --emit-native-project out/native-project
dotnet publish out/native-project -c Release -r win-x64
```
The implementation should use the supported `dotnet publish` NativeAOT pipeline. Do not couple SharpTS to private ILC command-line or MSBuild internals.
Both compiler SKUs may expose the target:
- **Managed SharpTS:** preferred initial implementation; retains open-world compile-time reference resolution.
- **Native SharpTS:** may drive the same external publish pipeline, but keeps #1324's closed type-universe limitations. It cannot regain arbitrary compile-time `-r` loading merely because the final output is NativeAOT.
- **SharpTS.Sdk:** remains a managed/RID-neutral build tool, but should eventually provide the clean production `dotnet publish` integration.
## Compatibility contract
Ship this as an explicit closed-world target.
The first experimental profile should support ordinary self-contained TypeScript and direct, statically resolvable BCL use. It should reject a program before native publish when emission requires any of:
- `SharpTSRuntimeRequirements.RuntimeAssembly`
- `SharpTSRuntimeRequirements.FullDependencyClosure`
- `SharpTSRuntimeRequirements.ManagedCompilerHost`
That initially excludes or gates current late-bound paths such as `eval`, `Proxy`, `Intl`, `vm`, worker/cluster bridges, compiled `child_process.fork`, and dynamic .NET event binding. Features may be restored later only through an AOT-safe static-link/generated-bridge design.
Dynamic assembly/plugin loading after publication is permanently outside the NativeAOT contract. Third-party libraries must be statically included in the publish closure and must themselves be trimming/AOT-compatible.
## Workstreams
### 0. Design and contract
- [ ] Write an ADR defining `native-aot` versus existing `dll` and bundled `exe` targets.
- [ ] Set CLI/MSBuild property names, RID behavior, output layout, debug-symbol behavior, and diagnostic codes.
- [ ] Define the experimental feature profile and the rule for graduating features into the supported profile.
- [ ] Decide whether NativeAOT warnings fail by default (recommended: every non-baselined SharpTS-owned warning fails).
### 1. Native publish driver
- [ ] Emit a stable, C#-nameable entry facade such as `SharpTS.Generated.EntryPoint.Run(string[] args)`.
- [ ] Generate an SDK publish project containing the guest assembly, external managed references, runtime/native assets, and AOT metadata.
- [ ] Invoke `dotnet publish` with `PublishAot=true` and an explicit RID.
- [ ] Preserve the generated project with `--emit-native-project` for diagnostics and reproducible CI builds.
- [ ] Forward ILC/linker output as structured SharpTS diagnostics.
- [ ] Add incremental caching and safe temporary-directory cleanup.
### 2. Generated-output compatibility analysis
- [ ] Add `NativeAotCompatibilityReport`; do not reuse the host's AOT analyzer inventory.
- [ ] Inspect `RequiredSharpTSRuntimeRequirements` before starting ILC and issue named feature diagnostics.
- [ ] Record emitted runtime helpers/features so diagnostics explain the source construct that made the output incompatible.
- [ ] Add an exact IL2xxx/IL3xxx warning ratchet for generated assemblies.
### 3. AOT-safe emitted runtime
- [ ] Replace or close reflection fallbacks based on `GetType().GetMethod/GetField/GetProperty`.
- [ ] Replace `$TSFunction`/callback `MethodInfo.Invoke` paths with generated invocation stubs or a finite dispatch catalog.
- [ ] Replace dynamic constructor activation with generated constructor factories where required.
- [ ] Remove the runtime `MakeGenericType` dependency in `SymbolClosedOwner`, or pre-generate every required closed shape.
- [ ] Make generated stack-trace capture trimming-safe or explicitly reduce the native profile's stack metadata contract.
- [ ] Fix any ILC-invalid generated methods exposed by reachability (the spike exposed `$Runtime.ConsoleClear`).
- [ ] Continue runtime tree shaking: `RuntimeFeatureDetector` currently documents that `$Runtime` and several Tier-B types are emitted unconditionally.
- [ ] Emit the narrowest necessary `DynamicDependency` / `DynamicallyAccessedMembers` metadata only where reflection is provably closed and safe.
### 4. Soft-dependent features
- [ ] For the first profile, fail before output when generated code needs `SharpTS.dll` at runtime.
- [ ] Evaluate statically linking the AOT-compatible SharpTS runtime/interpreter for `eval`/`vm` and similar features.
- [ ] If retained, replace `Type.GetType("..., SharpTS")` bridges with direct/generated calls and measure the binary-size cost.
- [ ] Keep `child_process.fork` rejected until it has a design that does not assume a managed compiler process/runtime closure.
### 5. .NET interop
- [ ] Support direct BCL calls whose members and generic instantiations are statically known.
- [ ] Add managed-SKU publish closure handling for `-r` assemblies and their runtime/native assets.
- [ ] Reuse/extend `NativeDotNetCatalog` for native-host and closed dynamic-dispatch scenarios.
- [ ] Verify dependency AOT compatibility and report unsupported third-party assemblies without hiding upstream warnings.
- [ ] Document that no compiler SKU can load a new managed plugin after native publication.
### 6. SharpTS.Sdk integration
- [ ] Add a documented `PublishAot`/`SharpTSTarget=native-aot` path to `SharpTS.Sdk` without turning the SDK itself into a native/RID-specific package.
- [ ] Make the SharpTS-generated assembly and entry facade part of the supported NativeAOT publish graph.
- [ ] Preserve ordinary `dotnet build` managed-IL behavior when NativeAOT is not requested.
### 7. CI, tests, and release
- [ ] Add a focused generated-output corpus for core language/runtime features.
- [ ] Publish and execute native guest programs on win-x64/arm64, linux-x64/arm64, and osx-x64/arm64 on appropriate runners.
- [ ] Run final executables with no installed/runtime-discovered `dotnet` dependency.
- [ ] Add expected-refusal tests for every excluded feature.
- [ ] Exercise classes, closures/callbacks, async/generators, modules, exceptions, collections, regex, JSON, networking/crypto, P/Invoke, and closed .NET interop.
- [ ] Verify portable TypeScript PDB/sequence-point behavior through NativeAOT native symbols and document limitations.
- [ ] Benchmark publish time, binary size, startup, memory, and steady-state performance against managed DLL and bundled EXE output.
- [ ] Add release documentation that clearly distinguishes the native compiler SKU from native generated output.
## Acceptance criteria for the first supported release
- [ ] At least the managed SharpTS SKU and `SharpTS.Sdk` can produce a true NativeAOT executable through a supported .NET publish flow.
- [ ] The native SharpTS SKU either exposes the same target within its closed-universe contract or returns a precise documented prerequisite/compatibility diagnostic.
- [ ] The allowed feature corpus publishes with zero unreviewed SharpTS-owned trim/AOT warnings.
- [ ] Every allowed corpus program runs correctly as a native executable without JIT or an installed .NET runtime.
- [ ] Unsupported source features fail before native linking, name the feature, and suggest managed DLL/bundled EXE output.
- [ ] External references are statically included, AOT-checked, and never dynamically loaded after publication.
- [ ] Cross-platform release jobs prove the supported RID matrix on matching operating systems.
- [ ] Documentation no longer uses ambiguous “AOT compilation” language without distinguishing IL AOT, NativeAOT compiler host, and NativeAOT program output.
## Toolchain and platform constraints
This option necessarily requires the .NET SDK/ILC pack plus the target platform's native compiler/linker prerequisites. The existing SDK-free PE-Packer `--target exe` path remains valuable and should not be replaced.
NativeAOT output is RID-specific. Cross-OS compilation is not supported by the .NET NativeAOT toolchain; release builds therefore need native runners, containers/VMs, or remote CI for each target OS. Cross-architecture compilation is possible only where the corresponding native toolchain is installed.
## Delivery recommendation
1. Land the entry facade plus `--emit-native-project` as an experimental spike.
2. Establish the generated-output warning ratchet and conservative feature gates.
3. Make a core TypeScript corpus warning-free and release `--target native-aot` as experimental from the managed SKU.
4. Expand closed runtime/interop coverage based on measured demand.
5. Add native-SKU parity and graduate the target only after the cross-RID acceptance matrix is green.
Full open-world parity with managed output is not a goal: it conflicts with NativeAOT's no-dynamic-loading/no-runtime-code-generation model. Broad closed-world TypeScript support is realistic.
## References
- Host/distribution epic: #1324
- Repository design notes: `docs/plans/native-aot.md`
- Emitted runtime dependency contract: `Compilation/SharpTSRuntimeRequirements.cs`
- Current runtime feature-gating status: `Compilation/RuntimeFeatureDetector.cs`
- .NET NativeAOT overview: https://learn.microsoft.com/dotnet/core/deploying/native-aot/
- .NET NativeAOT cross-compilation constraints: https://learn.microsoft.com/dotnet/core/deploying/native-aot/cross-compile
- .NET trimming guidance: https://learn.microsoft.com/dotnet/core/deploying/trimming/fixing-warnings
Contributor guide
Research direction
Start with docs/plans/native-aot.md, Compilation/SharpTSRuntimeRequirements.cs, and Compilation/RuntimeFeatureDetector.cs to understand the proposed contract and existing feature gates. Then reproduce the referenced NativeAOT spike and inspect the generated entry point and publish flow. Done means the defined corpus publishes through supported dotnet publish commands, unsupported features fail with precise diagnostics, and the acceptance matrix is covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, typescript
- Domain
- build-system, cli, compilers, devtools
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100