找回密码
 立即注册
查看: 664|回复: 0

[笔记] Unity mono代码结构分析及阅读(五)——初探JIT编译引擎

[复制链接]
发表于 2020-11-29 11:09 | 显示全部楼层 |阅读模式
这是基于unity mono代码阅读的第五篇。
上文已经实现了跟unity一样从dll找到我们需要调用的函数,并且初始化调用的过程。
在上文中,我们遇到了一个函数mono_runtime_invoke,这个函数就是从非托管的环境进入托管环境的一个必经之路,从这个函数出发,我们就从C++构建的非托管环境进入了CLR,然后由CLR解码并运行包含在dll中的CIL。
mono_runtime_invoke的函数看起来比较简单
  1. /**
  2. * mono_runtime_invoke:
  3. * @method: method to invoke
  4. * @obJ: object instance
  5. * @params: arguments to the method
  6. * @exc: exception information.
  7. *
  8. * Invokes the method represented by @method on the object @obj.
  9. *
  10. * obj is the 'this' pointer, it should be NULL for static
  11. * methods, a MonoObject* for object instances and a pointer to
  12. * the value type for value types.
  13. *
  14. * The params array contains the arguments to the method with the
  15. * same convention: MonoObject* pointers for object instances and
  16. * pointers to the value type otherwise.
  17. *
  18. * From unmanaged code you'll usually use the
  19. * mono_runtime_invoke() variant.
  20. *
  21. * Note that this function doesn't handle virtual methods for
  22. * you, it will exec the exact method you pass: we still need to
  23. * expose a function to lookup the derived class implementation
  24. * of a virtual method (there are examples of this in the code,
  25. * though).
  26. *
  27. * You can pass NULL as the exc argument if you don't want to
  28. * catch exceptions, otherwise, *exc will be set to the exception
  29. * thrown, if any.  if an exception is thrown, you can't use the
  30. * MonoObject* result from the function.
  31. *
  32. * If the method returns a value type, it is boxed in an object
  33. * reference.
  34. */
  35. MonoObject*
  36. mono_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObject **exc)
  37. {
  38.         MonoObject *result;
  39.         if (mono_runtime_get_no_exec ())
  40.                 g_warning ("Invoking method '%s' when running in no-exec mode.\n", mono_method_full_name (method, TRUE));
  41.         if (mono_profiler_get_events () & MONO_PROFILE_METHOD_EVENTS)
  42.                 mono_profiler_method_start_invoke (method);
  43.         result = default_mono_runtime_invoke (method, obj, params, exc);
  44.         if (mono_profiler_get_events () & MONO_PROFILE_METHOD_EVENTS)
  45.                 mono_profiler_method_end_invoke (method);
  46.         return result;
  47. }
复制代码
但是一般这种注释比函数主体都要长的函数都是狠角色。不千万不能小看。简单的看一下代码,这个似乎包含了一个对函数运行时间的Profile,然后就调用了default_mono_runtime_invoke
这个default_mono_runtime_invoke是何许人也?
  1. static MonoObject*
  2. dummy_mono_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObject **exc)
  3. {
  4.         g_error ("runtime invoke called on uninitialized runtime");
  5.         return NULL;
  6. }
  7. static MonoInvokeFunc default_mono_runtime_invoke = dummy_mono_runtime_invoke;
复制代码
正常看代码多的人可能能知道这个是动态赋值,搜搜这个default_mono_runtime_invoke 在什么地方赋值即可。
  1. /**
  2. * mono_install_runtime_invoke:
  3. * @func: Function to install
  4. *
  5. * This is a VM internal routine
  6. */
  7. void
  8. mono_install_runtime_invoke (MonoInvokeFunc func)
  9. {
  10.         default_mono_runtime_invoke = func ? func: dummy_mono_runtime_invoke;
  11. }
复制代码
然后再搜一下mono_install_runtime_invoke的引用。。
说实话,这种方法的确可以,但是有点绕,有没有别的方法呢?
有的,本系列文章之所以花那么大的篇幅介绍怎样搭建一个可以调试的mono环境,就是为了方便少看这种跳过来跳过去的问题。
我们直接F5走起,
在我们需要查看的代码处F9打上一个断点,然后F5,命中断点后,一路F11,很快就发现都到了
mono_jit_runtime_invoke,看名字这个就是CLR关键的函数了。这个函数可以简单翻翻。。我勒个去,这个函数实在是看起来头大。。
如果真的要硬着头皮啃下去,是在是有头秃。
那么有没有别的方法呢?
有的,遇事不决,先google,通过关键词 mono_jit_runtime_invoke加上一些源码,代码这些词语,我们可以筛选找出下面几篇高质量mono的分析文章(希望以后俺的文章也会出现在其中吧)。
上面三篇文章都是分析mono_jit_runtime_invoke调用过程的,十分值得一读。希望读者能点进去认真读一下。
从上面文章我们可以大致知道了mono_jit_runtime_invoke的调用过程。
  1. mono_runtime_invoke(method, NULL, pa, exc) // 要调用的方法,如"ClassName::Main()"
  2.         default_mono_runtime_invoke() / 实际上是调用了mono_jit_runtime_invoke()
  3.             info->compiled_method = mono_jit_compile_method_with_opt(method) // 编译目标函数
  4.             info->runtime_invoke = mono_jit_compile_method() // 编译目标函数的runtime wrapper
  5.                 mono_jit_compile_method_with_opt(method, default_opt, &ex)
  6.             runtime_invoke = info->runtime_invoke
  7.             runtime_invoke(obj, params, exc, info->compiled_method)  // 调用wrapper,wrapper会调用目标方法
复制代码
mono_runtime_invoke通过调用mono_jit_compile_method_withopt来编译需要编译的CIL函数,编译后的函数并不是直接被调用,而是通过一个runtime_invoke的代码包裹了一层后再进行调用的。
至于mono_jit_compile_method_withopt的实现,上面的文章也有一定的解释,
  1. mono_jit_compile_method_with_opt()
  2.     mono_jit_compile_method_inner()
  3.         mini_method_compile(method, opt, target_domain, TRUE, FALSE, 0) // 通过JIT编译给定方法
  4.             mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_bblock, MonoBasicBlock *end_bblock,
  5.                    MonoInst *return_var, GList *dont_inline, MonoInst **inline_args,
  6.                    guint inline_offset, gboolean is_virtual_call)//将二进制的字节码翻译为mono内部的字节码
  7.         mono_runtime_class_init_full() // 初始化方法所在对象
  8.             method = mono_class_get_cctor() // 得到类的构造函数
  9.             if (do_initialization) // 对象需要初始化
  10.                 mono_runtime_invoke() // 调用相应构造函数来构造对象,如"System.console:.cctor()"
  11.                     mono_jit_runtime_invoke()
复制代码
主要是通过mono_jit_compile_method_inner来将CIL字节码编译成当前机器可以识别的机器码,而
看雪的这篇文章,详细的分析了mono_method_to_ir的实现细节,mono_method_to_ir中通过遍历*ip 指向的opcode,通过与在mono\cil\opcode.def的定义进行解码和翻译的。而实际上笔者在查看mono_method_to_ir的调用函数的时候发现实际上mono_method_to_ir只负责解码,真正把解码后的逻辑翻译为机器码的过程是在mono_codegen函数中
在mono_codegen函数中,解码后的opcode会用一个一个的monobasicblock链来表示,而mono_codegen需要做的就是遍历这个block链,然后沿着链一个一个的用mono_arch_output_basic_block来生成对应的机器码。
如果我们想查看mono_arch_output_basic_block定义,我们会发现,这个函数是平台相关的,x86,arm都有对应的实现。我们打开一个x86的实现看一下。
发现这就是一个庞大的opcode翻译器,通过一个一个写好的X86指令集,来翻译已经被mono_method_to_ir解码过的il指令。
到此,我们基本上可以对mono jit编译器的流程有了一个大概的了解。
  1. mono_jit_compile_method_with_opt()
  2.     mono_jit_compile_method_inner()
  3.         mini_method_compile(method, opt, target_domain, TRUE, FALSE, 0) // 通过JIT编译给定方法
  4.             mono_method_to_ir(...)//将二进制的字节码翻译为mono内部的字节码
  5.             mono_codegen()//将mono内部的字节码翻译为机器码
  6.                mono_arch_output_basic_block()//将一个一个的opcode链转换为实际的写好的机器码。
  7.               
复制代码
这里面随便找一个函数都有差不多1000行,就不在本文做啰嗦的分析了,有兴趣的同学,可以在我们已经构建好的调试环境的基础上对这几个函数打一下断点,然后进行详细的调试和查看。
是不是mono的jit引擎只是如此??其实并不是,这里面一直没涉及一个mono jit逻辑里面一个很重要的概念Trampolines,如果只是从命名上来看,弹簧床??虽然名字看起来很奇怪,但是事实是,这个是mono jit引擎里面一个超级重要的概念。至于这个概念到底有多重要。
我们下回分解~

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 00:01 , Processed in 0.134461 second(s), 29 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表