infinite_l88p
infinite_l88p

Reputation: 31

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

The MIPS jump register (JR) instruction is frequently found in the binary of C++ code. As a result, what feature in C++ makes use of JR instructions and why does it use these instructions?

Upvotes: 0

Views: 518

Answers (1)

phuclv
phuclv

Reputation: 41952

Branch instructions can only be used for cases where the target address is known at compile time and is within a small range of the current instruction. You can't (easily) use it for branching to an address that isn't known statically and must be calculated/loaded at run time, or to jump to a target that's too far away

So here are some examples where JR or JALR must be used (both are exactly the same except that JALR stores the current address to return later):

  • Jumping to arbitrary addresses: Static branch instructions can't be used to jump to a 32-bit or 64-bit address because the immediate is only 16 or 26 bits long. You need to load the full address in a register and jump with JR/JALR

  • Function pointers: The function to be called is only known at run time, so obviously you need some way to call it dynamically

    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);
    }
    

    Functions in shared libraries (*.dll, *.so...) are also unknown to the processes before they're loaded, so if you load those libraries manually (with LoadLibrary(), dlopen()...) you'll also get the addresses to a function pointer and call them with JR/JALR. Typically a function will be called with JALR but if it's at the end of a function and tail-call optimization is enabled then JR will be used instead

    Vtable in many OOP languages like C++ is also an example of function pointer:

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

    Vtable demo on Godbolt's Compiler Explorer

  • Jump table (like in a big switch block)

    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 compiles the above code to

    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
    

    You can see the full output on Compiler Explorer

    $L4 is a jump table containing the address of the place you're branching to, which is the case blocks in this snippet. Its address is stored in $2 and jr needs to be used to move the instruction pointer to that address. j $2 is shown above but I think it's a disassembler bug, since j can't receive a register operand. Once you're at the correct case then jr is again used to call the f function pointer

See also Necessity of J vs. JAL (and JR vs. JALR) in MIPS assembly

Upvotes: 1

Related Questions