Skip to content

Port SPMI tools to C# (PublishAot) - #136984

Open
am11 wants to merge 6 commits into
dotnet:mainfrom
am11:feature/managed-superpmi
Open

am11 wants to merge 6 commits into
dotnet:mainfrom
am11:feature/managed-superpmi

Conversation

@am11

@am11 am11 commented Nov 4, 2026

Copy link
Copy Markdown
Member

superpmi, mcs and the collector, counter and simple shims are now C#, published with NativeAOT under the same names, command lines, outputs and locations, so they are drop-in replacements; the native sources are deleted.

  • SuperPmi.Shared has the method context format and the recording, replay and dumping of the JIT-EE calls. superpmi and the shims reuse crossgen2's JIT interface (CorInfoImpl_generated.cs and its native JitInterfaceWrapper).
  • The shims call the EE through native forwarders generated by the ThunkGenerator, which capture the EE's exceptions and rethrow them at the boundaries. The native glue is linked in statically.
  • The output matches native on Linux and Windows x64: replay, mcs, collections and call counts. The win-x86 and linux-arm64 cross builds were checked too.

superpmi, mcs and the collector, counter and simple shims are now C#,
published with NativeAOT under the same names, command lines, outputs
and locations, so they are drop-in replacements; the native sources
are deleted.

- SuperPmi.Shared has the method context format and the recording,
  replay and dumping of the JIT-EE calls. superpmi and the shims reuse
  crossgen2's JIT interface (CorInfoImpl_generated.cs and its native
  JitInterfaceWrapper).
- The shims call the EE through native forwarders generated by the
  ThunkGenerator, which capture the EE's exceptions and rethrow them
  at the boundaries. The native glue is linked in statically.
- The output matches native on Linux and Windows x64: replay, mcs,
  collections and call counts. The win-x86 and linux-arm64 cross
  builds were checked too.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 4, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
10 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Nov 4, 2026
@am11

am11 commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

@jkoritzinsky, @jkotas, @jakobbotsch: the JIT-EE interface layer is shared with crossgen2/ILC; the rest is a C# rewrite that fast-forwards SuperPMI to the point where it no longer depends on the CoreCLR PAL, which is why I started on this during the PAL cleanup. The remaining C++ is about 600 lines of glue that uses only the JIT-EE interface headers. Based on my testing, it's a drop-in replacement: same binaries in the same places, same command lines and outputs, and no CI changes needed.

@jkotas
jkotas requested a review from a team November 4, 2026 22:37
@jkotas

jkotas commented Nov 4, 2026

Copy link
Copy Markdown
Member

@dotnet/jit-contrib Thoughts about this rewrite?

It sounds good to me, as long as it does not negatively impact team productivity and validations based on superpmi.

@AndyAyersMS

Copy link
Copy Markdown
Member

@dotnet/jit-contrib Thoughts about this rewrite?

It sounds good to me, as long as it does not negatively impact team productivity and validations based on superpmi.

This is something we've thought about doing but never tried. I'd say it is worth serious consideration.

  • We don't really have a test suite. Some MCS verbs are rarely used or only used during specialized collections, so we should test those carefully. Streaming SPMI is almost never used but it would be nice to know it still works.
  • We should verify performance is comparable
  • We would need to be sure the resulting binaries could still be handled properly by PIN for our TP measurements.

Comment thread src/coreclr/jit/compiler.h Resolved
@am11

am11 commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Thanks @AndyAyersMS.

  • Testing: I ran native and managed side by side on the same inputs and diffed the outputs (with addresses masked):
    • superpmi: 86 comparisons (JIT and R2R collections) covering -c/-stride/-repeatCount/-matchHash/-emitMethodStats, all -v levels and -w, -jitoption/-jit2option/force/-ignoreStoredConfig, -f/-r/-failureLimit/-boa, asmdiffs with -details and the near differ, -p (incl. with details/failures/asmdiffs), -streaming (comments and per-method options), and argument errors.
    • mcs: every verb, including the rare ones (-ASMDump, -fracture, -integ, -jitflags, -dumpMap, ...); byte-identical, except that native -dump segfaults partway through a large JIT collection.
    • Collector: crossgen2 collections give same-size .mc files whose dumps differ only in code addresses; runtime collections replay with zero failures in both superpmis; counter CSVs identical; shims leave the R2R output unchanged.
    • Linux x64, Windows x64 and win-x86; linux-arm64 cross build checked too.
  • Performance: I've triggered the SuperPMI pipelines on this PR, so we can compare replay/asmdiffs/tpdiff timings against main.
  • PIN: Instrumentor_GetInsCount is still exported from the superpmi binary itself (the native glue is linked in statically). On Linux I had missed native's --export-dynamic; that's fixed. The pintool only counts instructions in the clrjit images, so superpmi being C# doesn't affect TP numbers; the tpdiff legs will confirm.

@am11
am11 force-pushed the feature/managed-superpmi branch 2 times, most recently from a8c6eec to cdb024b Compare November 5, 2026 10:35
@am11
am11 force-pushed the feature/managed-superpmi branch from cdb024b to 4db2623 Compare November 5, 2026 11:12
@JulieLeeMSFT

Copy link
Copy Markdown
Member
  • We would need to be sure the resulting binaries could still be handled properly by PIN for our TP measurements.

We should verify if it is handled by TPDiff properly as well because we are thinking to utilize TPDiff for Arm PRs.
Reference: #135627 - Investigate using TPDiff on Arm64

@am11

am11 commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Other vs. this PR
imageimage

Performance-wise we are good.

@jakobbotsch

Copy link
Copy Markdown
Member

I'm not so sure about making superpmi-shim-collector rely on NativeAOT. In bring-up scenarios I would expect that we want to be able to record SuperPMI collections well before we have a working NativeAOT for the target platform.

I don't have too many concerns about moving the other tooling and replaying side, however.

@am11

am11 commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

In bring-up scenarios I would expect that we want to be able to record SuperPMI collections well before we have a working NativeAOT for the target platform.

These days, we typically have a working coreclr+nativeaot runtimes ported in one shot, since most of the asm helpers are shared. Besides, we start needing SPMI type of tooling once we have a working stack. NativeAOT went from an optional to a required component on desktop platforms (where we run the spmi host).

@jakobbotsch

Copy link
Copy Markdown
Member

In bring-up scenarios I would expect that we want to be able to record SuperPMI collections well before we have a working NativeAOT for the target platform.

These days, we typically have a working coreclr+nativeaot runtimes ported in one shot, since most of the asm helpers are shared. Besides, we start needing SPMI type of tooling once we have a working stack. NativeAOT went from an optional to a required component on desktop platforms (where we run the spmi host).

Well, working NativeAOT depends on working JIT. If the JIT is not working my go to dev loop would have me using natively compiled superpmi-shim-collector on the target when isolating the repro.

@am11

am11 commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Well, working NativeAOT depends on working JIT.

AFAIK, (and I think it’s written in the porting docs) new platforms are always cross compiled during the port work, and we use host platform toolchain to emit for the target until the port is completed. At least this is how five platforms were ported in past few years.

@jakobbotsch

Copy link
Copy Markdown
Member

Well, working NativeAOT depends on working JIT.

AFAIK, (and I think it’s written in the porting docs) new platforms are always cross compiled during the port work, and we use host platform toolchain to emit for the target until the port is completed. At least this is how five platforms were ported in past few years.

Sure, how superpmi-shim-collector and coreclr are compiled for for the target does not matter much to this workflow.
My point is that if superpmi-shim-collector depends on NativeAOT then it also depends on the JIT working, but superpmi-shim-collector would be central to getting the JIT into a working state in the first place in my workflow.

@am11

am11 commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

My point is that if superpmi-shim-collector depends on NativeAOT then it also depends on the JIT working, but superpmi-shim-collector would be central to getting the JIT into a working state in the first place in my workflow.

I don't think the workflow changes in practice:

  • Before the runtime runs on the target, collections come from crossgen2/ILC on the host (with the shim loaded into the host compiler and the cross-targeting JIT), and replay runs on the host. All of that is host code compiled by the SDK's ILCompiler, so the JIT under bring-up isn't involved.
  • Once collecting on the target, the runtime there is already running CoreLib and the app, both compiled by that same JIT. The shim is simple code on top of that, so it doesn't add a meaningful dependency on the JIT: if a JIT'd "hello world" runs on CoreCLR, the JIT is mature enough for NativeAOT to compile this simple C# library.
  • What it does add is a dependency on the NativeAOT runtime on the target, which is a small increment over CoreCLR. On linux-x64, the runtime code NativeAOT doesn't share with CoreCLR (the GC, eventpipe and minipal are shared) is about 4% of CoreCLR's, and the shared part keeps growing. So the window where CoreCLR works on a new target but NativeAOT is small, and NativeAOT is effectively a required part of a port these days rather than an optional follow-up.

@jakobbotsch

Copy link
Copy Markdown
Member

I don't think the workflow changes in practice:

  • Before the runtime runs on the target, collections come from crossgen2/ILC on the host (with the shim loaded into the host compiler and the cross-targeting JIT), and replay runs on the host.

I do not think bring-ups like LoongArch64 had working crossgen2/ILC until a much later point-in-time. All the focus was on coreclr/corerun and the initial work was around running trivial examples with corerun. It is also the approach pointed at by https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/guide-for-porting.md.

I assume most of this PR is LLM-coded? It seems likely that during bring-up an LLM could just create an ad-hoc native version of superpmi-shim-collector if necessary, so in practice this may not even matter.

@am11

am11 commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Yup, at the time LA64 and RV64 were ported, nativeaot and surrounding infra was handwritten. These days we are in a much better situation a) due to more code sharing (e.g. src/native/minipal, src/coreclr/minipal, src/coreclr/runtime etc. are shared layers) and b) LLM can accelerate going from zero to working hello-world app on a new target over a weekend: #128783.

@jkotas

jkotas commented Nov 6, 2026

Copy link
Copy Markdown
Member

I agree with @am11 that that the historical way of doing new architecture ports is not necessarily the best way of doing new architecture ports going forward. #128783 suggests that NAOT is a better target for initial bring up. Also, I expect that we will eventually get to a place where AOT/R2R compilation is required for the JITed runtime startup. In that world, doing AOT bring up first is going to be the only viable path.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants