2015-09-25

MIPS, part 1: Registers and calling convention

In 1994 and 1995, Sony released its PlayStation console. It would soon be followed, in 1996 and 1997, by Nintendo's Nintendo 64 console. What both of them had in common, as briefly described in the entry "Why Old Games, New Tech?", was that they both contained processors running the MIPS architecture.

The MIPS architecture was a very large departure from the previous generation of consoles, sometimes known as the 16-bit era, due to the heavy focus on bits:

  • MIPS has a data cache and an instruction cache. It may not need to access memory for a while if it's running code in the instruction cache, and the code is loading data in the data cache. Processors of the previous generation have no caches whatsoever. Every instruction is fetched anew, and every piece of data is fetched anew.
  • MIPS has 31 registers usable for computations. It can load many bits of data from memory into these registers, then do many things on the loaded values in 1 cycle before storing them back. Processors of the previous generation have very few registers usable for computations. Some even have only 1 register, the accumulator. A lot of the code in games of this generation load data from memory, perform an operation, then store it back.
  • MIPS has a flat 32-bit memory model. Processors of the previous generation cannot access more than 64 kilobytes (65536 bytes) in one bank, and to access more data, they must use bank switching, which takes yet more time.
  • MIPS has a pipeline that allows it to run operations after a load if they don't depend on that load. It also has a store buffer that allows it to queue up stores that don't need to happen immediately. Previous processors access memory directly each time a memory operation is used.

On top of that, because MIPS has so many registers and because they're so fast, the designers of the architecture saw another opportunity for optimization in the software calling convention.

Programs organized in subroutines usually called each other by pushing arguments on the stack, then issuing a call instruction that pushed the return address on the stack and modified the Program Counter. The called subroutine then had to retrieve the arguments by loading from the stack.

What MIPS did instead was to pass as many arguments as possible in its registers (see $4 .. $7 below), then issue a call instruction that wrote the return address into a register (see $31 below) and modified the Program Counter. The called subroutine then retrieved the arguments from registers. This minimized stack access considerably.

Here's a summary of the calling convention on MIPS. (#registers)

Registers Status Usage notes
$0 Read-only Hard-wired to always contain the value 0. Used to quickly write zeroes to memory or other registers.
$1 Temporary If the assembler needs multiple instructions to implement something, it uses this register to calculate intermediate results.
$2 .. $3 Temporary At the end of functions, return values are stored here. The registers may have unrelated values before that.
$4 .. $7 Temporary At the start of functions, argument values are stored here. The registers may have unrelated values after that.
$8 .. $15 Temporary
$16 .. $23 Saved If a subroutine wishes to use one of these, it must save its previous value to its stack frame and restore it on exit.
$24 .. $25 Temporary
$26 .. $27 Reserved Exception handlers must save the running program's registers, but they wouldn't have any to run their own code if it weren't for these two.
$28 Saved This is the global pointer register. Position-independent code uses this. Video game consoles don't.
$29 Saved This is the stack pointer register, growing down by convention. It's saved by virtue of each decrement having a matching increment.
$30 Saved This is the frame pointer register. Some code uses this to make stack-walking easier in debuggers. Video game consoles don't.
$31 Temporary This is the return address register. The address of the instruction following a subroutine call is written here.

Now, I'd show a typical subroutine using these registers, but we haven't even seen the instruction set yet. That'll be for next time!

No comments :

Post a Comment