Skip to content
Andrew Au edited this page Sep 23, 2023 · 4 revisions

ILSpy.ReadyToRun

ILSpy.ReadyToRun is a new plugin introduced in ILSpy 6.0 Preview 2 to inspect the disassembly of the native code generated in a ReadyToRun binary for .NET Core.

Background

ReadyToRun can be roughly understood as the next generation of NGEN. Unlike NGEN, ReadyToRun compiled binaries can be run on any computer with the same operating system and architecture it was compiled for. Also, it does not require re-compilation if its dependencies outside of the current version bubble is changed. These make it possible for developers to deliver ReadyToRun compiled binaries to their customers.

The key advantage of ReadyToRun is start-up time improvement. Since methods are natively compiled, the runtime can simply load the natively compiled code during startup instead of spending time compiling them. This can often bring noticeable startup time improvement.

Tutorial

In a .NET Core application folder, instead of running dotnet run, publish the application using ReadyToRun by running

dotnet publish -r win-x64 /p:PublishReadyToRun=true

In addition to what was available, the published binaries will contain an extra folder named R2R. The ReadyToRun binary is available there for execution. For our purpose, we will inspect using ILSpy.ReadyToRun.

Opening the generated ReadyToRun binary using ILSpy. You will notice that it looks just like a regular .NET assembly, you can see the references, types, methods, all as usual. This is because a ReadyToRun binary contains all the information a .NET assembly used to have using the same format, so ILSpy worked just fine for them, even without the plugin.

To inspect the native code, you can switch to the ReadyToRun language in the language selector, select a method, the disassembly will be displayed in the code pane.

As of 1-1-2020, only code is natively compiled. Therefore, for any tree nodes other than method (e.g. classes and fields), there is nothing particular to show.

Use cases

While it is cool to be able to inspect the generated assembly code, why would one need do that? Here are some possible use cases where this can be helpful.

Diagnose issues

In theory, ready to run published code should run exactly the same (functionally) as the usual case do. In practice, you bet, especially on the bleeding edge (e.g. preview builds). Being able to inspect the disassembly code could be helpful to understand what might have gone wrong.

Optimization

In principle, ready to run published code should run faster. In practice, your mileage might vary. To make ready to run applicable on all matching computers without recompilation, the compiler cannot assume many things. For example, it cannot assume the AVX instruction set, which can make certain application slower.

Using COMPLUS_JitDump, you can inspect the jitted disassembly, and you can compare it with the ready to run compiled code.

Architecture

ILSpy.ReadyToRun is implemented as an ILSpy plugin. The key class in the implementation is ReadyToRunLanguage. This small class mostly delegates its work to R2RReader to obtain the native code and then use the Decoder to produce a textual disassembly.

R2RReader is implemented here in the dotnet runtime repo. The code is currently used to power R2RDump, a tool used internally in the dotnet runtime team to debug ready to run related issues. In fact, the ILCompiler.Reflection.ReadyToRun library was part of R2RDump, it was factored out of the tool to make this project possible. As such, the implementation of ILCompiler.Reflection.ReadyToRun is currently fairly coupled to the needs of R2RDump. This is slowly changing towards a general-purpose library.

Decoder is implemented the iced repo. The disassembler is currently used as a black box that performs the disassembly work without any changes.

TODO

ILSpy.ReadyToRun is still very rough. Contributions are very welcomed.

Functionalities improvement