.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

结尾

以上参考如下:
Net7的AOT和CLR有什么区别?
.Net7的R2R,Crossgen2是什么?
.Net7的AOT原理简析
C#之ILC和C++的CLR前者更快?
.Net7 AOT彻底解析下(完结篇)
.Net7 CLR和ILC编译函数过程
.Net AOT--Win11搭建和编译 X64 汇编
.Net7新编译器 ILC 简析
.Net7 CLR和ILC编译函数过程
.Net Native Code

作者:江湖评谈

你可能感兴趣的:(.net,c++,开发语言)