.Net8的AOT是如何被C++操控運行的
前言
.Net目前有兩條線,一條是正宗的.Net虛擬機CLR調用JIT的即時編譯,另外一條就是通過ILC編譯成本地的機器碼也即是AOT。上一篇【C++是如何運行C#/.Net的?】說的是前者,本篇來看下后者。
概括
前情提要:本篇以最新的.Net8 PreView5為藍本,進行的描述。
1.不同簡要比較
AOT相當于一個全新的縮減.Net版本,它和即時編譯器也即JIT機器碼照樣不同,這里舉一個例子,比如以下代碼:
static void Main(string[] args)
{
Program pm = new Program();
}
簡單的一個對象實例化,即時編譯里面:
call JIT_TrialAllocSFastMP_InlineGetThread (07FFC4C650650h)
mov qword ptr [rbp+20h],rax
mov rcx,qword ptr [rbp+20h]
call Program..ctor() (07FFBECC2C078h)
可以看到它先分配內存,然后調用默認的構造函數.ctor
那么AOT呢?
00007FF72AD459E8 48 8D 0D E1 55 17 00 lea rcx,[repro_Program::`vftable' (07FF72AEBAFD0h)]
00007FF72AD459EF E8 9C 0A C9 FF call RhpNewFast (07FF72A9D6490h)
它這里很明顯用了虛函數表指針作為參數,調用了RhpNewFast。完全是不一樣的。
2.整體過程AOT的編譯如下:C#源碼-》Roslyn(DLL)->ILC(Obj)->Link(Exe)寫好了C#源代碼之后,Roslyn會接管C#源代碼把它編譯成中間語言MSIL,存放在托管的動態鏈接庫即DLL里面。ILC會接管托管的DLL把它生成目標文件.Obj,然后用NativeAot的引導程序也即Bootstrap引導Link.exe工具鏈接.Obj目標文件生成可執行文件。
3.細節生成的目標文件也即Obj依舊是通過開源界三大編譯器之一的LLVM來生成的.在Windows/Linux/MaoOS上的動態鏈接庫分別是:
objwriter.dll(pe)/libobjwriter.so(elf)/libobjwriter.dylib(Mach-O)
他們分別封裝了各個平臺的llvm后端代碼生成來完成了Obj目標文件的生成。
4.C++和AOT無論是Roslyn,或者ILC或者引導程序BootStrap都是通過C++來啟動運行的。1.Roslyn的運行實質上是運行在虛擬機CLR上面的2.ILC同上3.BootStrap它本身就是cpp項目而llvm本身就是一套超級底層的C/C++項目,可以看到在一整套的AOT編譯運行流程中,C++始終操控C#的運行。
5.核心代碼
為了更為透徹的了解到ILC調用Objwriter.dll動態鏈接庫操控llvm生成obj目標文件。在WinX64平臺上,這里演示一段簡單的代碼,步驟如下:
一.首先在nuget上面下載一個ILC編譯器,也即是:
runtime.win-x64.Microsoft.DotNet.ILCompiler
二.找到nuget目錄,里面有個objwriter.dll一般的在如下路徑:
C:\Users\Administrator\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\7.0.8\tools
三.新建一個C#控制臺項目名字Obj,把上面的路徑找到的objwriter.dll放入到
Obj項目bin/Debug/net7.0目錄下面。
四.Obj項目bin/Debug/net7.0目錄下面新建一個Demo.obj目標文件
五.Program.cs里面填寫如下代碼:
internal class Program
{
[DllImport("objwriter.dll")]
private static extern IntPtr InitObjWriter([MarshalAs(UnmanagedType.LPUTF8Str)] string objectFilePath, string triple = null);
[DllImport("objwriter.dll")]
private static extern void FinishObjWriter(IntPtr objWriter);
[DllImport("objwriter.dll")]
private static extern void EmitIntValue(IntPtr objWriter, ulong value, int size);
private IntPtr _nativeObjectWriter = IntPtr.Zero;
static void Main(string[] args)
{
IntPtr objectWriter = InitObjWriter("Demo.obj", "x86_64-pc-win32-windows");
EmitIntValue(objectWriter, 0x10, 4);
FinishObjWriter(objectWriter);
}
}
運行這段代碼之后,打開Demo.obj可以看到文件里面寫入了一段內容,這就是ILC編譯器往obj目標文件里面寫入被JIT編譯后的機器碼的核心部分代碼的原型。這里因為封裝了llvm的細節,又因托管省略了大部分,看起來比較簡潔。綜合起來實際上的代碼高達百萬行之巨,暫不贅述此部分。
以上代碼GitHub下載地址:
https://github.com/tangyanzhi/jianghupt/releases/download/llvm/Obj.rar