How does MIPS store a number larger than 32 bits?

1

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?

assembly
memory
mips
mips32
bigint
asked on Stack Overflow Nov 10, 2018 by PyFox • edited Sep 12, 2020 by Peter Cordes

1 Answer

6

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

answered on Stack Overflow Nov 10, 2018 by Peter Cordes • edited Sep 12, 2020 by Peter Cordes

User contributions licensed under CC BY-SA 3.0