运行时 IL 生成Wrappers机制解析.NET runtime 中 Mono 的运行时动态生成 IL 技术【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime导读.NET 是面向云、移动、桌面与 IoT 应用的跨平台运行时其 Mono 运行时src/mono在运行期间会大量动态生成 IL 方法这类方法被称为 wrappers包装器。本文以仓库设计文档 runtime-ilgen.md 为骨架结合 wrapper-types.h、marshal.c、method-builder-ilgen.c 等源码实现系统讲解 wrapper 的定义、WrapperInfo 描述结构、缓存唯一性保证、泛型膨胀路径与 AOT 预编译支持并逐一剖析 managed-to-native、runtime-invoke、delegate-invoke 等各类 wrapper 的用途。读完本文你将理解 Mono 如何在运行时就地合成方法来完成互操作编组、反射调用、委托派发、同步加锁、数组访问等底层机制。为什么运行时需要动态生成 IL在 IL中间语言执行模型中许多高层语言特性并不直接对应一条机器指令而是需要一段额外的胶水代码。Mono 选择在运行时生成这些代码而不是在 JIT 编译阶段内联展开原因在于可复用性同一段编组逻辑可以被成千上万个方法共享运行时只生成一次并缓存与 JIT 解耦生成的是标准 IL 方法JIT 只需按常规方式编译它无需为每种场景定制编译路径AOT 友好这些 IL 方法也可以在编译期full-aot被收集并预编译为原生代码。在 Mono 运行时中这类运行时生成的 IL 方法统一被称为wrapper包装器——因为它们中的一部分包裹着其他方法例如 managed-to-native wrapper 会包裹被调用的原生函数负责完成参数/返回值编组、异常处理EH结构搭建等职责。每个 wrapper 方法都会在MonoMethod结构上设置wrapper_type字段用于标识其 wrapper 类型。源码结构总览根据设计文档与运行时 IL 生成相关的源码分为三组均位于 src/mono/mono/metadata文件组职责wrapper-types.h所有 wrapper 类型的枚举定义WRAPPER(...)宏表marshal*marshal.c、marshal.h、marshal-shared.c、marshal-lightweight.c生成各类 wrapper 的函数族method-builder*method-builder.c、method-builder-ilgen.c 等运行时创建新 IL 方法/代码的低层设施wrapper 类型枚举wrapper-types.h 是定义 wrapper 类型的唯一权威来源其开头的注释耐人寻味/* * NOTE NOTE NOTE * No additional wrapper types should be added. * If a new wrapper is absolutely necessary, an existing one needs * to be removed first (with all the change that implies). */该文件通过WRAPPER(name, string)宏定义枚举成员完整清单如下对应 wrapper-types.hWRAPPER(NONE, none) WRAPPER(DELEGATE_INVOKE, delegate-invoke) WRAPPER(DELEGATE_BEGIN_INVOKE, delegate-begin-invoke) WRAPPER(DELEGATE_END_INVOKE, delegate-end-invoke) WRAPPER(RUNTIME_INVOKE, runtime-invoke) WRAPPER(NATIVE_TO_MANAGED, native-to-managed) WRAPPER(MANAGED_TO_NATIVE, managed-to-native) WRAPPER(MANAGED_TO_MANAGED, managed-to-managed) WRAPPER(SYNCHRONIZED, synchronized) WRAPPER(DYNAMIC_METHOD, dynamic-method) WRAPPER(CASTCLASS, castclass) WRAPPER(STELEMREF, stelemref) WRAPPER(UNBOX, unbox) WRAPPER(WRITE_BARRIER, write-barrier) WRAPPER(OTHER, other) WRAPPER(ALLOC, alloc)可见 wrapper 类型是一个受控的、封闭的集合新增类型需要先移除既有类型以保证运行时与 AOT 编译器、调试器之间协议稳定。method-builder低层 IL 构造设施MonoMethodBuilder 是运行时构建 IL 方法的核心抽象它通过回调表MonoMethodBuilderCallbacks与具体实现解耦typedef struct _MonoMethodBuilder MonoMethodBuilder; typedef struct { MonoMethodBuilder* (*new_base) (MonoClass *klass, MonoWrapperType type, gboolean dynamic); void (*free) (MonoMethodBuilder *mb); MonoMethod* (*create_method) (MonoMethodBuilder *mb, MonoMethodSignature *signature, int max_stack); } MonoMethodBuilderCallbacks;new_base在指定类上新建一个 method builder并标明 wrapper 类型与是否动态free释放 buildercreate_method根据签名与max_stack最大栈深度把已生成的 IL 固化为一个MonoMethod。IL 版本的实现位于 method-builder-ilgen.c其中有一个值得注意的约定见 method-builder-ilgen.c 与 method-builder-ilgen-internals.h// note: idx 1 is the wrapper info (see mono_marshal_get_wrapper_info) /* placeholder for the wrapper always at index 1, see mono_marshal_set_wrapper_info */即方法数据method_data数组的index 1 固定存放 wrapper 的WrapperInfo这是下文mono_marshal_get_wrapper_info能直接读取的前提。WrapperInfowrapper 的身份描述每个 wrapper 都关联一个WrapperInfo结构用于描述该 wrapper 的附加信息。该结构的定义位于 marshal.h/* * This structure contains additional information to uniquely identify a given wrapper * method. It can be retrieved by mono_marshal_get_wrapper_info () for certain types * of wrappers, i.e. ones which do not have a 1-1 association with a method/class. */ typedef struct { WrapperSubtype subtype; union { RuntimeInvokeWrapperInfo runtime_invoke; StringCtorWrapperInfo string_ctor; ElementAddrWrapperInfo element_addr; VirtualStelemrefWrapperInfo virtual_stelemref; NativeToManagedWrapperInfo native_to_managed; ManagedToNativeWrapperInfo managed_to_native; SynchronizedWrapperInfo synchronized; SynchronizedInnerWrapperInfo synchronized_inner; GenericArrayHelperWrapperInfo generic_array_helper; ICallWrapperInfo icall; ArrayAccessorWrapperInfo array_accessor; AllocatorWrapperInfo alloc; UnboxWrapperInfo unbox; GsharedvtWrapperInfo gsharedvt; DelegateInvokeWrapperInfo delegate_invoke; InterpInWrapperInfo interp_in; AOTInitWrapperInfo aot_init; LLVMFuncWrapperInfo llvm_func; NativeFuncWrapperInfo native_func; UnsafeAccessorWrapperInfo unsafe_accessor; } d; } WrapperInfo;结构由两部分构成WrapperSubtype subtypewrapper 的子类型。一些 wrapper 存在多种变体需要通过子类型进一步区分。子类型枚举同样在 marshal.h 定义例如WRAPPER_SUBTYPE_RUNTIME_INVOKE_NORMAL普通反射调用、WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL/BOUND虚拟/绑定委托调用、WRAPPER_SUBTYPE_GSHAREDVT_IN_SIG/OUT_SIG共享泛型值类型签名、WRAPPER_SUBTYPE_INTERP_IN、WRAPPER_SUBTYPE_AOT_INIT、WRAPPER_SUBTYPE_LLVM_FUNC、WRAPPER_SUBTYPE_UNSAFE_ACCESSOR等。union d按子类型存放的具体信息例如NativeToManagedWrapperInfo记录被调用的method与klassElementAddrWrapperInfo记录多维数组的rank维数与elem_size元素大小AllocatorWrapperInfo记录 GC 名称gc_name与分配类型alloc_typeGenericArrayHelperWrapperInfo记录目标klass、helper 名称与methodUnsafeAccessorWrapperInfo记录UnsafeAccessor的kind、被访问成员名member_name与方法。写入与读取WrapperInfo的读写由 marshal.c 中的一对函数完成mono_marshal_set_wrapper_info把WrapperInfo写入方法的method_data[1]槽位。值得注意的是它对MONO_WRAPPER_NONE与MONO_WRAPPER_DYNAMIC_METHOD两种类型直接返回——这两类方法不携带WrapperInfo详见下文 Dynamic-method 一节。mono_marshal_get_wrapper_info以mono_method_get_wrapper_data (wrapper, 1)读取该结构并断言wrapper_type非零WrapperInfo* mono_marshal_get_wrapper_info (MonoMethod *wrapper) { g_assert (wrapper-wrapper_type); return (WrapperInfo *)mono_method_get_wrapper_data (wrapper, 1); }mono_wrapper_info_create从方法所在 image 的内存分配器中分配WrapperInfo并初始化subtypewrapper 元数据与被包装方法同生命周期随 image 一起卸载。wrapper 的宿主类wrapper 方法需要挂靠在某个类上。为什么不能统一放进 mscorlib 的某个类get_wrapper_target_class 的注释给出了三条设计考量wrapper 引用了元数据签名因此必须放入与被包装方法相同的 image保证两者一起卸载放入带类型初始化器type initializer的类可能触发初始化副作用且 wrapper 是共享的应避免放入膨胀类inflated class可能因 image 卸载导致类被删除而出问题。最终结论wrapper 被放入 image 的Module类动态 image 则放入wrappers_type。这一细节解释了 wrapper 与宿主程序集生命周期强绑定的原因。缓存 wrapper保证唯一性设计文档强调wrappers should be unique, i.e. there should be only one instance of every wrapper——同一 wrapper 只能存在一个实例。这是通过按 wrapper 类型划分的哈希表缓存实现的缓存存放在MonoMemoryManager.wrapper_caches中。在源码中MonoWrapperCaches结构定义于 loader-internals.h挂载在内存管理器上同文件 loader-internals.hGHashTable *managed_wrapper_cache; GHashTable *native_wrapper_cache; GHashTable *native_wrapper_aot_cache; GHashTable *native_wrapper_aot_check_cache; GHashTable *unbox_wrapper_cache; ... MonoWrapperCaches wrapper_caches;marshal.c 中到处可以看到按类型取缓存、查缓存、未命中则生成并回填的模式例如delegate 相关缓存delegate_invoke_cache、delegate_invoke_virtual_cache、delegate_begin_invoke_cache、delegate_end_invoke_cache、delegate_bound_static_invoke_cache、delegate_abstract_invoke_cacheruntime-invoke 缓存runtime_invoke_method_cache以方法为键哈希/比较函数为 wrapper_cache_method_key_hash 系列与runtime_invoke_signature_cache以签名为键见 wrapper_cache_signature_key_hashnative wrapper 缓存native_wrapper_cache、native_wrapper_aot_cache、native_wrapper_aot_check_cacheicall 缓存icall_wrapper_cache。这种先查缓存、后生成的两段式结构典型的如mono_marshal_get_runtime_invoke_full中先查runtime_invoke_method_cache再在签名缓存中查找/创建保证了同一参数的 wrapper 全局唯一也让 AOT 场景下能够命中编译期预生成的版本。泛型与 wrapper 的膨胀路径泛型实例的 wrapper 不能直接为每个实例化单独生成——那样既浪费又无法 AOT。设计文档给出了明确的膨胀inflation路径instance method - generic method definition - generic wrapper - inflated wrapper即先从泛型实例方法回溯到泛型方法定义为定义生成通用 wrapper再基于具体泛型上下文MonoGenericContext膨胀出实例 wrapper。源码中check_generic_wrapper_cachemarshal.c与check_generic_delegate_wrapper_cachemarshal.c正是这条路径的实现——它们先查定义级 wrapper 缓存再判断能否复用/膨胀。marshal.c 第 1841 行附近的注释还给出了一个具体例子为SortT生成的 wrapper 需要按泛型定义生成、再为具体实例膨胀这是 full-aot 能正常工作的前提。AOT 支持编译期收集与反序列化在 full-aot 模式下运行时无法在运行期即时生成 IL因此AOT 编译器会在编译期收集应用所需的全部 wrapper 并预编译运行期直接复用。整个过程涉及WrapperInfo结构的序列化/反序列化。源码中的对应证据包括use_aot_wrappers开关与 mono_marshal_use_aot_wrappers控制是否优先使用 AOT 预生成的 wrappermono_marshal_get_native_wrapper 带有aot参数且会先查native_wrapper_aot_cache/native_wrapper_aot_check_cache两个 AOT 缓存marshal.c命中即返回 AOT 版本未命中再走 IL 生成路径mono_marshal_get_native_func_wrapper_aot 与native_func_wrapper_aot_cache为委托原生调用入口wrapper_aot_native提供 AOT 版本mono_marshal_get_aot_init_wrapper生成模块初始化时执行的 AOT 初始化 wrapper其子类型由MonoAotInitSubtype枚举marshal.h定义包括AOT_INIT_METHOD、AOT_INIT_METHOD_GSHARED_MRGCTX、AOT_INIT_METHOD_GSHARED_THIS、AOT_INIT_METHOD_GSHARED_VTABLE四种缓存释放路径marshal.c同时清理native_wrapper_aot_cache等印证 AOT 缓存是 wrapper 缓存体系的一等公民。一句话总结AOT 场景下wrapper 的生成从运行期前移到编译期运行期只负责查表命中。Wrapper 类型逐一解析设计文档按用途把 wrapper 分为若干大类下面结合源码逐一展开。Managed-to-native托管到原生职责发起对原生代码的调用。它们负责编组参数与返回值如字符串编码转换、指针/结构体转换、搭建 EH 异常处理结构等。典型入口mono_marshal_get_native_wrapper 系列。marshal.c 中大量mono_marshal_get_*_conv函数如 mono_marshal_get_string_to_ptr_conv、mono_marshal_get_ptr_to_string_conv负责把编组策略翻译为具体 IL 发射序列。P/Invoke 调用的完整 IL 生成在 marshal-lightweight.c 的emit_native_icall_wrapper_ilgen中实现它支持check_exceptions是否检查异常与aot两种模式。Native-to-managed原生到托管职责让原生代码能够回调托管方法。当委托被传给原生代码时原生侧拿到的是一个 native-to-managed wrapper。其描述结构 NativeToManagedWrapperInfo 记录委托目标method与声明类klass。原生代码通过该 wrapper 进入托管世界由 wrapper 完成从原生调用约定到托管调用约定的转换包括异常处理、GC 安全点等。Delegate-invoke委托调用用途处理 JIT 快速路径fastpath无法覆盖的更复杂的委托调用场景。JIT 对常见的委托调用会生成快速内联路径当调用涉及虚拟分发、绑定静态方法首参、抽象方法等复杂情形时则回退到 delegate-invoke wrapper。源码中由 mono_marshal_get_delegate_invoke、mono_marshal_get_delegate_invoke_internal支持callvirt、绑定首参、目标方法等参数与 mono_marshal_get_delegate_invoke_subtype 生成子类型包括WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL虚拟方法委托WRAPPER_SUBTYPE_DELEGATE_INVOKE_BOUND绑定静态方法首参的委托。委托的BeginInvoke/EndInvoke异步调用模型也有独立 wrappermono_marshal_get_delegate_begin_invoke 与 mono_marshal_get_delegate_end_invoke对应DELEGATE_BEGIN_INVOKE/DELEGATE_END_INVOKE类型。Synchronized同步加锁用途包装同步方法[MethodImpl(MethodImplOptions.Synchronized)]或 JIT 判定需要同步的方法由 wrapper 负责加锁/解锁。描述结构 SynchronizedWrapperInfo 与 SynchronizedInnerWrapperInfo 分别对应外层同步 wrapper 与其内层方法 wrapper——外层做锁获取/释放内层执行真实方法体。Runtime-invoke反射调用用途实现 mono_runtime_invoke含virtual_变体——即反射场景下按MonoMethod 参数数组进行调用。其描述结构 RuntimeInvokeWrapperInfo 记录目标method与签名sig。由于反射调用需要把void **args参数数组拆箱/装箱到具体签名wrapper 成为签名适配器。源码提供了多种获取途径mono_marshal_get_runtime_invoke按方法获取mono_marshal_get_runtime_invoke_for_sig仅按签名获取用于不需要具体方法的场景mono_marshal_get_runtime_invoke_dynamic动态方法的反射调用专用。缓存上区分方法键缓存runtime_invoke_method_cache与签名键缓存runtime_invoke_signature_cache、runtime_invoke_sig_cache前者优先后者兜底兼顾精确性与缓存命中率。Dynamic-method动态方法说明这类方法并不是真正的 wrapper而是用户代码通过DynamicMethod类创建的方法。关键区别它们没有关联的WrapperInfo结构。这与mono_marshal_set_wrapper_info中对MONO_WRAPPER_DYNAMIC_METHOD直接 return 的断言逻辑完全一致marshal.c——动态方法由用户完全控制 IL运行时无需也无法为其维护 wrapper 描述。Alloc分配器用途SGEN 分代 GC 的分配器方法。描述结构 AllocatorWrapperInfo 记录gc_name与alloc_type。这类 wrapper 在 sgen-mono.c 中生成同文件第 296 行调用mono_marshal_set_wrapper_info是 GC 分配快速路径的组成部分。Write-barrier写屏障用途SGEN 写屏障方法。在分代 GC 中向托管堆写入引用时必须维护卡表card table等屏障结构write-barrier wrapper 封装了这些写入逻辑。Castclass类型转换用途实现复杂的类型转换cast。当 JIT 无法用快速路径完成类型检查时例如接口转换、带可变泛型参数的转换回退到 castclass wrapper 执行完整的分派逻辑。Stelemref数组元素写入用途实现stelem.ref向引用类型数组写元素的复杂场景。marshal.h 定义了精细的 stelemref 子策略枚举展示了按运行时类型信息做渐进式检查的优化思路typedef enum { STELEMREF_OBJECT, /* no check at all */ STELEMREF_SEALED_CLASS, /* check vtable-klass-element_type */ STELEMREF_CLASS, /* only the klass-parents check */ STELEMREF_CLASS_SMALL_IDEPTH, /* like STELEMREF_CLASS but without the idepth check */ STELEMREF_INTERFACE, /* interfaces without variant generic arguments */ STELEMREF_COMPLEX, /* arrays, MBR or types with variant generic args - go straight to icalls */ ... } StelemrefKind;从完全不检查STELEMREF_OBJECT到直接走 icallSTELEMREF_COMPLEX运行时按目标类型特性选择最省成本的检查策略。另有VirtualStelemrefWrapperInfo记录kind支撑虚接口场景的变体。Unbox拆箱用途调用值类型方法前对 receiverthis 参数进行拆箱。描述结构 UnboxWrapperInfo 记录目标method。当以装箱对象形式调用值类型方法时wrapper 先完成拆箱再转入真实方法。Managed-to-managed / other托管到托管及其他其余 wrapper 归入MANAGED_TO_MANAGED或OTHER大类通过WrapperSubtype子类型进一步区分。设计文档列举了以下子类型String-ctor字符串构造用途实现字符串构造函数。第一个参数被忽略直接分配一个新字符串。描述结构 StringCtorWrapperInfo 记录method。字符串构造在运行时被视为分配 填充首个显式参数通常是某种指针/引用不参与常规构造语义。Element-addr元素取址用途实现多维数组的ldelem.a取元素地址。描述结构 ElementAddrWrapperInfo 记录rank维数与elem_size元素大小wrapper 据此计算多维索引偏移。Generic-array-helper泛型数组辅助用途实现数组上的隐式接口如IListT等。wrapper 会委托给Array类上的 helper 方法。描述结构 GenericArrayHelperWrapperInfo 记录目标klass、helper 名name与method。CLR 规定T[]隐式实现IListT、IReadOnlyListT等接口这些接口方法需要按具体元素类型派发到Array类的对应实现。Structure-to-ptr / Ptr-to-structure结构体与指针互转用途Structure-to-ptr实现Marshal.StructureToPtrPtr-to-structure实现Marshal.PtrToStructure。这类 wrapper 负责在托管结构体与原生内存布局之间做按字段编组blittable 快速复制或逐字段转换是System.Runtime.InteropServices内存互操作能力的底层支撑。小结wrapper 机制的设计闭环将设计文档与源码对照可以勾勒出 Mono 运行时 IL 生成的完整设计闭环描述每个 wrapper 由WrapperInfomarshal.h唯一描述通过mono_marshal_set/get_wrapper_info读写固定在方法数据槽位 1生成marshal 系列函数基于MonoMethodBuildermethod-builder.h发射 IL由mono_mb_create_method固化为MonoMethod唯一性所有 wrapper 按类型进入MonoWrapperCachesloader-internals.h先查后建保证单实例泛型走实例方法 → 泛型定义 → 定义级 wrapper → 膨胀实例路径兼顾复用与 AOTAOT编译期收集、序列化WrapperInfo并预编译运行期通过*_aot_cache直接命中。理解了这一套机制也就理解了 .NET 互操作、反射、委托与数组抽象这些高层能力在 Mono 运行时底层的真实落地方式。如需深入某个环节建议从 marshal.c 的各类mono_marshal_get_*函数入手沿着WrapperInfo的读写与缓存命中路径逐步阅读。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考