解释器指令入口——栈顶缓存
解释器指令入口——栈顶缓存书接上回转发表的结构是栈顶状态和字节码值共同组成使用栈顶状态的原因是为了在特殊情况下提高解释器的执行速度。例1 栈顶状态前后一致假设由下列字节码执行序列iload_1 iaddiload_1字节码的含义是把本地变量表中的第1号元素整数放入栈顶在x86上就是在rax寄存器里保存第1号元素的值。假设现在操作数栈的布局如下iadd指令是把栈顶的两个数相加结果放入栈顶那么结果就是这种栈顶状态前后一致的操作只需要更新rax寄存器。其实现的汇编如下mov 本地变量表1的值, %rax pop %rdx add %rdx, %rax例2 栈顶状态前后不一致紧跟上面的指令运算结果现在执行下面的指令iload_2该字节码指令是把本地变量表的第2号元素推向栈顶即rax值不在是30而是最新值30会压入内存于是hotspot在进入该指令之前会判断当前的栈顶状态如果栈顶是整型则需要进行压栈操作具体如下push %rax mov 本地变量表2的值, %rax栈顶缓存在例子2中对已经处于栈顶的值进行了内存压栈操作而例子1中没有已在栈顶rax中的值进行操作。这种根据栈顶状态来决定是否将rax进行压栈的操作成为栈顶缓存。即上一条指令的结果究竟是放rax还是放内存实际是由下一条指令的操作决定的。在hotspot中的以下代码对每个字节码的进入栈顶状态和输出栈顶状态进行了定义/*templateTable.cpp*/// Java spec bytecodes ubcp|disp|clvm|iswd in out generator argumentdef(Bytecodes::_nop,____|____|____|____,vtos,vtos,nop,_);def(Bytecodes::_aconst_null,____|____|____|____,vtos,atos,aconst_null,_);def(Bytecodes::_iconst_m1,____|____|____|____,vtos,itos,iconst,-1);...其中in和out列对输入和输出的栈顶状态进行定义。例如iconst_m1字节码的输入栈顶状态应该是void输出栈顶状态时int为了实现例1和例2的不同操作hotspot在每条字节码的入口地址生成函数中对不同的栈顶状态进行了汇编生成/*templateInterpreterGenerator_x86.cpp*/voidTemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code){...if(Bytecodes::is_defined(code)){Template*tTemplateTable::template_for(code);assert(t-is_valid(),just checking);//正常字节码指令set_short_entry_points(t,bep,cep,sep,aep,iep,lep,fep,dep,vep);}if(Bytecodes::wide_is_defined(code)){Template*tTemplateTable::template_for_wide(code);assert(t-is_valid(),just checking);//宽字节码指令此处不表set_wide_entry_point(t,wep);}这里主要涉及代码如下//生成字节码指令的汇编片段Template*tTemplateTable::template_for(code);把不同栈顶状态的地址赋值set_short_entry_points(t,bep,cep,sep,aep,iep,lep,fep,dep,vep);其具体实现如下voidTemplateInterpreterGenerator::set_short_entry_points(Template*t,addressbep,addresscep,addresssep,addressaep,addressiep,addresslep,addressfep,addressdep,addressvep){assert(t-is_valid(),template must exist);switch(t-tos_in()){casebtos:caseztos:casectos:casestos:ShouldNotReachHere();// btos/ctos/stos should use itos.break;caseatos:vep__pc();__pop(atos);aep__pc();generate_and_dispatch(t);break;caseitos:vep__pc();__pop(itos);iep__pc();generate_and_dispatch(t);break;caseltos:vep__pc();__pop(ltos);lep__pc();generate_and_dispatch(t);break;caseftos:vep__pc();__pop(ftos);fep__pc();generate_and_dispatch(t);break;casedtos:vep__pc();__pop(dtos);dep__pc();generate_and_dispatch(t);break;casevtos:set_vtos_entry_points(t,bep,cep,sep,aep,iep,lep,fep,dep,vep);break;default:ShouldNotReachHere();break;}}可以看到当栈顶为bool,byte,char和short时统一按照不允许操作因为没有字节码指令进入栈顶状态为上述类型。如果进入栈顶状态是整型那么走itos分支生成的代码地址分为2部分1.如果进入状态是vtos即void那么首先做pop栈顶操作即把内存中的栈顶元素放入rax2.如果进入状态是itos即整型那么直接调用字节码指令汇编generate_and_dispatch(t);将vtos的地址赋值给vep将itos的地址赋值给iep栈顶缓存汇编现在举一个极端的例子——NOP指令该指令什么都不做进入状态应该是void输出状态是void如果上一条指令的输出栈顶状态是object则应该先将rax中的值压栈。下面是gdb调试的结果总结栈顶缓存的本质就是上一条指令放栈顶的位置由下一条指令决定
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2490614.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!