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.