Search code examples
assemblymemorymipsmips32bigint

How does MIPS store a number larger than 32 bits?


I was looking for an answer to this but couldn't find a clear one. Does it split the number between multiple registers or it can't simply cope with it? I tried testing it with MARS and using the number 4294967296 which is 0x100000000 but the register saved only 0x00000000, so the '1' bit is omitted. Is there a way to cope with such numbers?


Solution

  • Use 2 registers, an extra one for the high half. MIPS doesn't have flags, so there isn't a 2-instruction add/add-with-carry way to add int64_t like there is on many other ISAs, but you can look at compiler output for a C function that adds two 64-bit integers easily enough.

    #include <stdint.h>
    
    int64_t add64(int64_t a, int64_t b) { 
        return a+b;
    }
    

    compiled for MIPS on the Godbolt compiler explorer with gcc5.4 -O3 -fno-delayed-branch1:

    add64:
        addu    $3,$5,$7
        sltu    $5,$3,$5     # check for carry-out with  sum_lo < a_lo  (unsigned)
        addu    $2,$4,$6     # add high halves
        addu    $2,$5,$2     # add the carry-out from the low half into the high half
        j       $31          # return
        nop                # branch-delay slots
    

    Footnote 1: so GCC only fills the branch-delay slot with a NOP, not a real instruction. So the same code would work on a simplified MIPS without delay slots like MARS simulates by default.


    In memory, MIPS in big-endian mode (the more common choice for MIPS) stores the entire 64-bit integer in big-endian order, thus the "high half" (most significant 32 bits) is at the lower address, so the highest byte of that word is at the lowest address, and all 8 bytes are in descending order of place value.

    void add64_store(int64_t a, int64_t b, int64_t *res) { 
        *res = a+b;
    }
    
      ## gcc5.4 -O3 for MIPS - big-endian, not MIPS (el)
        addu    $7,$5,$7        # low half
        lw      $2,16($sp)
        sltu    $5,$7,$5        # carry-out
        addu    $4,$4,$6        
        addu    $5,$5,$4        # high half
        sw      $5,0($2)        # store the high half to res[0..3] (byte offsets)
        sw      $7,4($2)        # store the low  half to res[4..7]
        j       $31
        nop                   # delay slot
    

    As you can see from the register numbers used, the calling convention passes the high half in the lower-numbered register (earlier arg), unlike on little-endian architectures where the high-half goes in the later arg-passing slot. This makes things work as desired if you run out of register and an int64_t is passed on the stack.


    On an architecture with flags and an add-with-carry instruction (like ARM32 for example), you get an add instruction that creates a 33 bit result in C:R0 (top bit in the carry flag, lower 32 in a register).

    add64:
        adds    r0, r2, r0    @ ADD and set flags
        adc     r1, r3, r1    @ r1 = r1 + r3 + carry
        bx      lr
    

    You tagged this MIPS32, so you don't have 64-bit extensions to the ISA available. That was introduced in MIPS III in 1991, but for embedded use MIPS32 is a modern MIPS with extensions other than 64-bit registers.

    The same reasoning applies to 128-bit integers on 64-bit MIPS with daddu