C++的哪些功能利用了JR指令?

What feature of C++ makes use of JR instructions?

本文关键字:JR 指令 功能 C++      更新时间:2023-10-16

MIPS跳转寄存器(JR(指令经常出现在C++代码的二进制文件中。因此,C++中的哪些功能使用了JR指令,为什么使用这些指令?

分支指令只能用于目标地址在编译时已知且在当前指令的较小范围内的情况。您不能(轻松(使用它来分支到静态未知且必须在运行时计算/加载的地址,或者跳转到太远的目标

因此,这里有一些必须使用JRJALR的示例(两者都完全相同,只是JALR存储当前地址以供以后返回(:

  • 跳转到任意地址:静态分支指令不能用于跳转到 32 位或 64 位地址,因为即时地址只有 16 或 26 位长。您需要在寄存器中加载完整地址并使用JR/JALR跳转

  • 函数
  • 指针:调用函数仅在运行时已知,因此显然您需要某种方法来动态调用它

    int Add(int a, int b);
    int Sub(int a, int b);
    int Mul(int a, int b);
    int Div(int a, int b);
    int (*p[4]) (int x, int y) = { Add, Sub, Mul, Div };
    int test_function_pointer(int i, int x, int y) {
    return p[i](x, y);
    }
    

    共享库中的函数(*.dll,*.so...(在加载之前也不知道进程,因此,如果您手动加载这些库(使用LoadLibrary()dlopen()...(,您还将获得函数指针的地址并使用JR/JALR调用它们。通常,函数将使用JALR调用,但如果它位于函数的末尾并且启用了尾调用优化,则将使用JR

    Vtable在许多OOP语言中,如C++也是函数指针的一个例子

    struct A {
    virtual int getValue() = 0;
    };
    int test_vtable(A *a) {
    return a->getValue() + 1;
    }
    

    在Godbolt的编译器资源管理器上的演示

  • 跳台(就像在一个大开关块中(

    typedef int (*func)(int);
    int doSomething(func f, int x, int y)
    {
    switch(x)
    {
    case 0:
    return f(x + y);
    case 1:
    return f(x + 2*y);
    case 2:
    return f(2*x + y);
    case 3:
    return f(x - y);
    case 4:
    return f(3*x + y);
    case 5:
    return f(x * y);
    case 6:
    return f(x);
    case 7:
    return f(y);
    default:
    return 3;
    }
    }
    

    GCC 将上述代码编译为

    doSomething(int (*)(int), int, int):
    sltu    $2,$5,8
    beq     $2,$0,$L2 # x >= 8: default case
    move    $25,$4
    lui     $2,%hi($L4)
    addiu   $2,$2,%lo($L4)  # load address of $L4 to $2
    sll     $5,$5,2         # effective address = $L4 + x*4
    addu    $5,$2,$5
    lw      $2,0($5)
    nop
    j       $2
    nop
    $L4:
    .word   $L11
    .word   $L5
    .word   $L6
    .word   $L7
    .word   $L8
    .word   $L9
    .word   $L10
    .word   $L11
    $L11:
    jr      $25
    move    $4,$6
    $L9:
    sll     $4,$6,2
    jr      $25
    addu    $4,$4,$6
    # ... many more cases below
    

    您可以在编译器资源管理器上看到完整的输出

    $L4是一个跳转表,其中包含您要分支到的位置的地址,即此代码段中的case块。它的地址存储在$2中,需要使用jr将指令指针移动到该地址。j $2如上所示,但我认为这是一个反汇编器错误,因为j无法接收寄存器操作数。一旦你处于正确的大小写,那么jr将再次用于调用f函数指针

另请参阅MIPS组装中J与JAL的必要性(以及JR与JALR(的必要性