JIT编译器如何编译含非JIT函数调用及虚调用的函数?
JIT编译器的两个关键调用场景实现细节
一、如何编译包含非JIT函数调用的函数
当JIT编译出的本地代码需要调用未被JIT编译的函数时,核心通过**桩代码(Stub)**做中转,具体流程如下:
- 生成中转桩代码:JIT编译过程中,会在代码缓存里为每个未编译的外部函数生成一段桩代码。这段代码负责把本地代码的调用上下文(寄存器状态、栈帧参数等)转换成解释器能处理的格式,然后跳转到虚拟机的解释器入口。
- 触发延迟编译:当桩代码第一次被执行时,会触发JIT编译器对目标函数进行编译。编译完成后,直接修改桩代码的跳转目标为新生成的本地代码地址,后续调用就直接走本地执行路径,无需再经过解释器。
- 保留回退路径:如果目标函数因依赖动态代码、无法静态分析等原因无法被JIT编译,桩代码会一直保留,每次调用都转去解释器执行。
二、如何编译包含虚调用的函数(含托管语言虚拟机场景)
常规C/C++虚拟机的做法
对于C/C++实现的虚拟机,确实可以直接将vtable的内存地址硬编码到本地代码中——这类虚拟机的对象内存地址通常固定,不会被GC移动。但托管语言虚拟机需要解决GC压缩导致对象/vtable地址变化的问题,常用方案有:
- 间接引用表寻址:托管虚拟机不会把vtable的直接地址硬编码到本地代码,而是让本地代码通过一个全局固定的间接引用表访问vtable。GC压缩时,只需要更新表中对应的vtable条目地址,无需修改已生成的本地代码。
- 内联缓存(Inline Cache, IC):这是最常用的优化手段,JIT编译虚调用时会生成两种路径的代码:
- 快路径:检查当前对象的类型是否与之前调用过的类型一致,如果匹配,直接跳转到缓存的目标函数地址执行;
- 慢路径:如果类型不匹配,调用虚拟机的类型解析逻辑,找到正确的vtable和目标函数,更新内联缓存的内容后再执行目标函数。
- GC写屏障配合更新:当GC压缩移动对象时,会触发写屏障机制,自动更新所有指向该对象vtable的内联缓存或间接引用表条目,确保本地代码总能访问到正确的地址。
- 分层编译优化:在分层JIT中,低层级编译会先生成通用的虚调用逻辑(始终走虚拟机的类型解析);当代码成为热点进入高层级编译时,JIT会基于收集到的类型分布信息,生成更高效的内联缓存代码——如果发现虚调用的目标类型始终单一,甚至会直接将虚调用优化为普通的直接调用。
内容的提问来源于stack exchange,提问作者Dylech30th
相关产品推荐
相关产品推荐

