代码生成器必须把中间表示程序映射到(大牛用一文带你深入解析java虚拟机:C1编译器的编译流程)

编译流程

本节从源代码开始,简要介绍C1的中间表示和编译过程。以下部分将详细描述这些过程。

代码生成器

进入C1

当解释器找到一个热方法时,它会调用编译器Broker:: complex _ method()将一个编译任务交付给编译任务队列。当C1编译器线程在队列中找到一个编译任务时,它就会醒来,拉出编译任务,进入JIT编译器的世界。转向C1编译器线程,它最初阻塞在编译任务队列中,找到编译任务后唤醒,通过清单8-1所示的调用链后开始编译。



清单8-1 C1调用链

JavaThread::thread _ main _ entry() -& gt;compiler _ thread _ entry() -& gt;compiler broker::compiler _ thread _ loop() -& gt;编译broker::invoke _ compiler _ on _ method()//使用C1 -->;编译器::compile_method() //进入C1世界 -& gt;Compilation::Compilation() //代码编译 -& gt;compilation::compile _ method() -& gt;compile::compile _ Java _ method()C1的完整编译周期相当于编译对象的构造周期。编译::compile_method包括两个动作:编译代码和安装编译后的代码。compilation::compile _ Java _ method表示编译动作。你可以从这里开始阅读C1的源代码,如清单8-2所示。

代码清单 8-2编译::compile _ Java _ method

int compilation::compile _ Java _ method(){ {//构造hir phaseretimeit(_ t _ buildir); build _ hir(); } { //构造lir phaseretime time it(_ t _ emit _ lir); _frame_map = new FrameMap(...); emit _ lir(); } { //生成机器码 pharacetime time it(_ t _ code emit); return emit _ code _ body(); } }C1将Java字节码转换成各种形式的中间表示,然后在其上进行代码优化和机器码生成。这个机器码是C1的输出。可以看出,Java字节码和JIT产生的机器码之间的桥梁是中间表示,C1的大部分工作是对中间表示进行各种转换。

有一个巧妙的方法可以得到C1的详细工作流程:C1会对编译过程中每个小阶段的表演进行计时,以此命名为阶段名,这样你就可以通过计时查看详细的步骤,如清单8-3所示。

代码清单8-3 C1编译详细流程

Typenum { _ t _ compile,//C1编译 _t_setup,// 1)设置C1编译环境 _t_buildIR,// 2)构造hir _ t _ hir _ parse,。// GVN优化 _t_optimize_blocks,//基本块优化 _ t _ optimize _ null _ checks,//空检查优化消除 _ t _ rangecheckelimination,//数组范围检查消除 _t_linearScan,//线性扫描寄存器分配 _t_lirGeneration,//生成LIR _t_codeemit,//机器码生成 _ t _ codeinstall,/一般来说

高级中间表示

开发者用Java写代码,通过javac编译得到相对紧凑简洁的字节码。但即使是字节码,对编译器来说还是太高级了,所以编译器会用一种更适合编译优化的形式来表示字节码,这种形式被称为高级中间表示(Advanced Intermediate re presentation,HIR)。



HIR是由基本块组成的控制流图,SSA形式的指令序列在基本块内部。build_HIR()的第二阶段不仅要构造HIR,还要执行许多平台无关的代码优化,如清单8-4所示。

代码清单8-4 构造HIR

Void编译::build _ hir () { ... //Create hir { pharacetime time it(_ t _ hir _ parse); _hir = new IR(this,method(),osr _ BCI()); } ... //优化:条件表达式消除,基本块消除 if(use C1 optimizations){ needs _ cleanuphasetracetimeit(_ t _ optimize _ blocks); _ hir-& gt;optimize _ block(); } ... //优化:全局值编号优化 if(useglobalvaluenumbering){ pharacetime time it(_ t _ gvn); int instructions = Instruction::number _ of _ instructions(); GlobalValueNumbering gvn(_ hir); } //优化:范围检查消除 if(rangecheckelimination){ if(_ hir-->;osr _ entry()= = NULL){ PhaseTraceTime time it(_ t _ rangeCheckElimination); RangeCheckElimination::消除(_ hir); } } /Optimization:NULL检查并消除 If(use C1 optimizations){ needs _ clean up phaseatime time it(_ t _ optimize _ NULL _ checks _ hir-& gt;消除空值检查(); } }build_hir()第一阶段解析字节码生成hir[1];之后,它将检查HIR是否有效。如果无效,将出现编译分离。这时,编译器停止编译,然后退回到解释器。当HIR的检查通过时,C1将消除条件表达式和基本块。然后在使用GVN后消除一些数组范围检查;最后,进行空值检查并消除。另外需要注意的是,如果分层编译是用-XX:+TieredCompilation开始的,那么条件表达式消除和基本块消除只会发生在分层编译的第1级和第2级。

低级中间表示

中间表示屏蔽了特定架构的细节,使得优化更加方便。当这些优化完成后,为了贴近具体的架构,需要将高层的中间表示转化为低层的中间表示(LIR),然后基于LIR进行注册分配,如清单8-5所示。



代码清单8-5 emit_lir

Void编译::emit _ lir () { {/hir转换为lir phaseretime time it(_ t _ lir generation); hir()-& gt;iterate _ linear _ scan _ order(& gen); } { //寄存器分配。将LIR的虚拟寄存器映射到物理寄存器 pharacetime time it(_ t _ linear scan); linear scan * allocator = new linear scan(hir(),&gen,frame _ map()); 分配器-& gt;do _ linear _ scan(); ... }}}首先使用LIRGenerator将HIR转换为更低级的LIR,然后使用LinearScan根据线性寄存器分配算法将LIR中的虚拟寄存器映射到指令集架构允许的物理寄存器。清单8-6给出了HIR的直观表达式,它代表了a+b的简单加法运算,其中A和B是方法参数。

HIR由代码8-6 添加

B1->;B0 [0,0] Locals size 3[static jint addtest . add(jint,jint)] 0 i1[方法参数] 1 I2[方法参数] _ p _ _ BCI _ _ use _ _ tid _ _ instruction _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _(HIR) 。0 0 v6标准条目B0 B0 & lt;- B1 [0,5]STD Locals size 3[static Jint addtest . add(Jint,Jint)] 0i 1 1i 2 _ p _ _ BCI _ _使用__ tid _ _指令_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _(HIR

LIR由代码8-7 添加

B1->;B0 [0,0] _ NR _ _ instruction _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _(LIR) 0 label[label:0x 0000000125245 ea 0] 2 STD _ entry B0 & lt;- B1 dom B1 [0,5]STD _ NR _ _ instruction _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _(LIR) 10 label[label:0x 00000001252451d 0] 14 add[rsi | I][RDX | I][RSI | I] 16 move[RSI | I][rax | I] 18 return[rax | I][RSI | I]表示使用了物理寄存器RSI同理还有[R10|L],表示虚拟寄存器R10存储长值。[stack:0|I]表示堆栈的第0个槽存储int值。

[int:1|I]表示整数常数1。

当LIR完成寄存器分配后,Compilation::emit_code_body()会将LIR代码转换成机器码,emit_code_body()最终会将任务委托给LIR _汇编器。因为LIR代码类似于指令集表示,所以机器代码生成的过程可以看作是一个线性映射的过程,除了一些高级的LIR代码需要更多的汇编仿真。

本文给大家讲解的内容是深入解析java虚拟机:C1编译器,编译流程
  • 下篇文章将为您深入剖析java虚拟机:C1编译器,从字节码到HIR;;
  • 觉得文章不错的朋友可以转发此文关注边肖;
  • 谢谢大家的支持!
  • 您可以还会对下面的文章感兴趣

    使用微信扫描二维码后

    点击右上角发送给好友