前言
TypedocConverter是我先前因帮助维护monaco-editor-uwp但苦于monacoeditor的API实在太多,手写C#的类型绑定十分不划算而发起的一个项目。
这个工具可以将typedoc根据TypeScript生成的JSON文件直接生成对应的C#类型绑定代码,并提供完整的JSON序列化支持,因此使用这个工具可以大大降低移植TypeScript库到.NET上的困难。(至于为什么是从typedoc而不是从TypeScript直接parse,其实只是因为太懒了不想写TypeScript的parser)
TypedocConverter使用F#编写,虽然使用.NET5可以做到程序集裁剪后使用单文件自托管发布,但是我一直在想如果能使用AOT技术将整个程序编译为nativebinary那就好了,这样的话用户在使用的时候将不需要运行.NET的运行时,也不需要JIT,而是直接运行机器代码。
工具除了功能性之外,最重要的就是用户体验,这样做将大大提升程序的启动速度(虽然原本已经够快了,但是我想将ms的启动时间缩短到不到1ms),使得用户使用该工具时不需要任何的等待。
AOT方案调研
.NET一直以来都有一个叫做CoreRT的项目,使用该工具可以将.NET程序集编译到nativebinary,然而这个项目自从年官方就没有再积极维护。但是由于社区的强烈呼声以及某个微软的合作伙伴的项目需要AOT技术,并表示如果没有这项技术将不再使用.NET,于是这个项目原地复活,以NativeAOT的名字转移到了runtimelab并作为.NET6的P0(最高)优先级实验性工作项(即提供带支持的官方preview,而不再是原来的万年alpha),目前支持win-x64、linux-x64和osx-x64,对于ARM64、移动平台和浏览器(WebAssembly)的支持在计划当中。
借着这个契机,我决定使用该方案将项目编译为原生镜像。
NativeAOT原理
.NET的NativeAOT的思路其实很简单:
首先需要一个AOT友好的、用于NativeAOT的核心库(System.Private.CoreLib)实现,提供类型和实现查找、类型解析等方法扫描程序集,记录用到的类型和方法调用RyuJIT接口,生成类型的元数据,为所有的方法生成代码,最终产生出obj二进制文件调用链接器(MSVC或clang),将产生的obj与GC和系统库等链接成为最终的可执行文件现阶段NativeAOT基本已经完成,剩余的部分工作则是一些修补和完善,以及对新版本.NET的跟进(目前还没有跟进C#8之后牵扯到运行时修改的特性,如默认接口方法实现和模块初始化器等等)。
可能你会问这和.NETNative技术有何不同?不同之处在于.NETNative使用UTC编译器(MSVC后端)进行代码生成,而NativeAOT使用RyuJIT进行代码生成。
关于.NETNativeAOT完整的使用文档可以参考:using-native-aot。
针对NativeAOT改造项目
NativeAOT使用非常简单,只需要修改csproj项目文件即可:
PropertyGroupIlcOptimizationPreferenceSpeed/IlcOptimizationPreferenceIlcFoldIdenticalMethodBodiestrue/IlcFoldIdenticalMethodBodies/PropertyGroupItemGroupPackageReferenceInclude=Microsoft.DotNet.ILCompilerVersion=6.0.0-*//ItemGroupIlcOptimizationPreference指定Speed表示以最大性能为目标生成代码(如果指定Size则表示以最小程序为目标生成代码)。
IlcFoldIdenticalMethodBodies参数则可以将相同的方法体合并,有助于减小体积。
最后则是新的Microsoft.DotNet.ILCompiler,这是NativeAOT编译器本体,通过wildcard指定6.0.0-*版本,这样每次编译都会获取最新的版本。
由于Microsoft.DotNet.ILCompiler来自实验仓库的artifacts,而没有发布在官方的nuget源,需要新建nuget.config额外将实验仓库的artifacts作为源引入:
?xmlversion=1.0encoding=utf-8?configurationpackageSourcesaddkey=dotnet-experimentalvalue=