.Net8的AOT是如何被C++操控運(yùn)行的
前言
.Net目前有兩條線,一條是正宗的.Net虛擬機(jī)CLR調(diào)用JIT的即時(shí)編譯,另外一條就是通過ILC編譯成本地的機(jī)器碼也即是AOT。上一篇【C++是如何運(yùn)行C#/.Net的?】說的是前者,本篇來看下后者。
概括
前情提要:本篇以最新的.Net8 PreView5為藍(lán)本,進(jìn)行的描述。
1.不同簡要比較
AOT相當(dāng)于一個(gè)全新的縮減.Net版本,它和即時(shí)編譯器也即JIT機(jī)器碼照樣不同,這里舉一個(gè)例子,比如以下代碼:
static void Main(string[] args)
{
Program pm = new Program();
}
簡單的一個(gè)對象實(shí)例化,即時(shí)編譯里面:
call JIT_TrialAllocSFastMP_InlineGetThread (07FFC4C650650h)
mov qword ptr [rbp+20h],rax
mov rcx,qword ptr [rbp+20h]
call Program..ctor() (07FFBECC2C078h)
可以看到它先分配內(nèi)存,然后調(diào)用默認(rèn)的構(gòu)造函數(shù).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)
它這里很明顯用了虛函數(shù)表指針作為參數(shù),調(diào)用了RhpNewFast。完全是不一樣的。
2.整體過程AOT的編譯如下:C#源碼-》Roslyn(DLL)->ILC(Obj)->Link(Exe)寫好了C#源代碼之后,Roslyn會(huì)接管C#源代碼把它編譯成中間語言MSIL,存放在托管的動(dòng)態(tài)鏈接庫即DLL里面。ILC會(huì)接管托管的DLL把它生成目標(biāo)文件.Obj,然后用NativeAot的引導(dǎo)程序也即Bootstrap引導(dǎo)Link.exe工具鏈接.Obj目標(biāo)文件生成可執(zhí)行文件。
3.細(xì)節(jié)生成的目標(biāo)文件也即Obj依舊是通過開源界三大編譯器之一的LLVM來生成的.在Windows/Linux/MaoOS上的動(dòng)態(tài)鏈接庫分別是:
objwriter.dll(pe)/libobjwriter.so(elf)/libobjwriter.dylib(Mach-O)
他們分別封裝了各個(gè)平臺的llvm后端代碼生成來完成了Obj目標(biāo)文件的生成。
4.C++和AOT無論是Roslyn,或者ILC或者引導(dǎo)程序BootStrap都是通過C++來啟動(dòng)運(yùn)行的。1.Roslyn的運(yùn)行實(shí)質(zhì)上是運(yùn)行在虛擬機(jī)CLR上面的2.ILC同上3.BootStrap它本身就是cpp項(xiàng)目而llvm本身就是一套超級底層的C/C++項(xiàng)目,可以看到在一整套的AOT編譯運(yùn)行流程中,C++始終操控C#的運(yùn)行。
5.核心代碼
為了更為透徹的了解到ILC調(diào)用Objwriter.dll動(dòng)態(tài)鏈接庫操控llvm生成obj目標(biāo)文件。在WinX64平臺上,這里演示一段簡單的代碼,步驟如下:
一.首先在nuget上面下載一個(gè)ILC編譯器,也即是:
runtime.win-x64.Microsoft.DotNet.ILCompiler
二.找到nuget目錄,里面有個(gè)objwriter.dll一般的在如下路徑:
C:\Users\Administrator\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\7.0.8\tools
三.新建一個(gè)C#控制臺項(xiàng)目名字Obj,把上面的路徑找到的objwriter.dll放入到
Obj項(xiàng)目bin/Debug/net7.0目錄下面。
四.Obj項(xiàng)目bin/Debug/net7.0目錄下面新建一個(gè)Demo.obj目標(biāo)文件
五.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);
}
}
運(yùn)行這段代碼之后,打開Demo.obj可以看到文件里面寫入了一段內(nèi)容,這就是ILC編譯器往obj目標(biāo)文件里面寫入被JIT編譯后的機(jī)器碼的核心部分代碼的原型。這里因?yàn)榉庋b了llvm的細(xì)節(jié),又因托管省略了大部分,看起來比較簡潔。綜合起來實(shí)際上的代碼高達(dá)百萬行之巨,暫不贅述此部分。
以上代碼GitHub下載地址:
https://github.com/tangyanzhi/jianghupt/releases/download/llvm/Obj.rar