mips中关于j跳转和b跳转

先来看源码,该函数是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的实现中,image.png

错将mip中的bgeu当做延时跳转处理,因此在riscv的移植中,错误的先执行FETCH_ADVANCE_INST 2 ,更新rpc的参数,再执行bgeu a1, a3, 2f ;测试失败。gdb调试发现,在汇编解释执行处理op_aput指令时,如果发生异常,会通过MterpCommonFallback函数退出汇编解释执行,image.png

此时会给a0赋值为0,因此执行方法ExecuteMterpImpl的返回值为false,会跳转到C++解释执行ExecuteSwitchImpl中,去继续执行,该方法shadow_frame在对应rpc处的字节码。image.png

如果先执行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等指令,当跳转的函数有返回值时,没有延时跳转;当没有返回值时,有延时跳转。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值