前言
.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