先来看源码,该函数是mips64实现的对于aget_object的汇编实现:
%def op_aget_object():
/*
* Array object get. vAA <- vBB[vCC].
*
* for: aget-object
*/
/* op vAA, vBB, vCC */
.extern artAGetObjectFromMterp
lbu a2, 2(rPC) # a2 <- BB
lbu a3, 3(rPC) # a3 <- CC
EXPORT_PC
GET_VREG_U a0, a2 # a0 <- vBB (array object)
GET_VREG a1, a3 # a1 <- vCC (requested index)
jal artAGetObjectFromMterp # (array, index)
ld a1, THREAD_EXCEPTION_OFFSET(rSELF)
srl a4, rINST, 8 # a4 <- AA
PREFETCH_INST 2
bnez a1, MterpException
SET_VREG_OBJECT v0, a4 # vAA <- v0
ADVANCE 2
GET_INST_OPCODE v0 # extract opcode from rINST
GOTO_OPCODE v0 # jump to next instruction
在SET_VREG_OBJECT v0, a4
中,由于v0是返回寄存器,所以一开始理所当然认为是bnez a1, MterpException
中
MterpException
中的函数返回值,但是后来发现不是这样的。
之前基于riscv架构的函数调用时栈帧管理
我们对函数调用和跳转分析,比如jal跳转到一个函数,会把下一条代码的地址赋值给ra寄存器,当跳转到函数执行后,第一步就是保存ra到栈中,最后一步的ret是一条伪指令,其实是跳转到ra的地址去执行,这样就很好的完成了函数调用和返回操作。
那b类型的跳转是如何实现函数的返回的呢?从mips的操作中可以压根看到b系列的操作根本没有对ra进行操作,也就是意味着,这个b跳转压根就没打算返回。那么这个v0返回寄存器哪里来的?从何时赋值的?
目光转移到这里:jal artAGetObjectFromMterp # (array, index)
,jal是有返回值的啊,所以v0寄存器返回的是artAGetObjectFromMterp
函数的返回值。并且,jal本身是有延时槽的,也就是在正常情况下,jal在跳转之前都会先执行下一条指令,但是在有返回值的情况下,jal是不会延时的。比如:
jal MterpSetUpHotnessCountdown
move rPROFILE, v0 # Starting hotness countdown to rPROFILE
上面两行的意思就是跳转到MterpSetUpHotnessCountdown
中去执行,然后返回寄存器中保存着hotness的值,需要把值传递给rPROFILE
,在该情况下是没有延时槽的。
同样,在开头的代码中,jal artAGetObjectFromMterp # (array, index)
也是有返回值的,并不会延时执行,并且这个延时执行造成的错误是致命的。因为a0, a1存放的是artAGetObjectFromMterp
函数的入参,jal下一条指令是ld a1, THREAD_EXCEPTION_OFFSET(rSELF)
,这就意味着a1的值被修改了,入参也就被修改了。
来看一下翻译成通俗易懂的riscv的代码:
%def op_aget_object():
/*
* Array object get. vAA <- vBB[vCC].
*
* for: aget-object
*/
/* op vAA, vBB, vCC */
.extern artAGetObjectFromMterp
lbu a2, 2(rPC) # a2 <- BB
lbu a3, 3(rPC) # a3 <- CC
EXPORT_PC
GET_VREG_U a0, a2 # a0 <- vBB (array object)
GET_VREG a1, a3 # a1 <- vCC (requested index)
jal artAGetObjectFromMterp # (array, index)
ld a1, THREAD_EXCEPTION_OFFSET(rSELF)
srli a4, rINST, 8 # a4 <- AA
PREFETCH_INST 2
bnez a1, 1f
SET_VREG_OBJECT a0, a4 # vAA <- v0
ADVANCE 2
GET_INST_OPCODE t4 # extract opcode from rINST
GOTO_OPCODE t4 # jump to next instruction
1:
j MterpException
由于b系列的跳转在我们目前的环境下跳转范围为-2k~2k,所以我们必须通过j来跳转实现模块外部的调用。但是由于b系列的跳转是不需要返回的,此处的跳转我们也用了j,而不是jal。对于j跳转来说,是不保存ra的。
在进行ArrayIndexOutOfBoundsException测试时,发现在指令op_aput的实现中,
错将mip中的bgeu当做延时跳转处理,因此在riscv的移植中,错误的先执行FETCH_ADVANCE_INST 2 ,更新rpc的参数,再执行bgeu a1, a3, 2f ;测试失败。gdb调试发现,在汇编解释执行处理op_aput指令时,如果发生异常,会通过MterpCommonFallback函数退出汇编解释执行,
此时会给a0赋值为0,因此执行方法ExecuteMterpImpl的返回值为false,会跳转到C++解释执行ExecuteSwitchImpl中,去继续执行,该方法shadow_frame在对应rpc处的字节码。
如果先执行FETCH_ADVANCE_INST 2,则rpc会更新为aget的下一条字节码,那么在C++解释执行中,不会处理aget字节码的,即不会报出异常。因此应该先执行bgeu a1, a3, 2f。以上为为何两条riscv指令的顺序颠倒会导致,java程序运行时,不抛出异常的错误,及在mips当中bgeu没有延时跳转。后续通过查阅mips指令,以及目前遇到b跳转的情况,总结b跳转有延时跳转的指令有:b(无条件跳转),beq(=),bgez(>=0),blez(<=0),bltz(<0),bgtz(>0),bne(!=)。jal,j,jr, jalr等指令,当跳转的函数有返回值时,没有延时跳转;当没有返回值时,有延时跳转。