If you've worked on a Nintendo 64 project, you're probably used to the floating-point registers in MIPS assembly being numbered, in a rather random-looking way, as the even numbers from 0-30. Some seem to be used for returns, some for arguments, some for temps, but there doesn't seem to be any much logic to it. And indeed there isn't, especially for the temp registers:
f0,f2are return values (likev0,v1)f12,f14are function arguments (likea0,a1,...)- The other even-numbered ones are temporary and saved registers
- Odd-numbered ones show up occasionally if doubles are about
Wouldn't it be nice if there was a better way of labelling them? There actually is: the o32 ABI specifies names for the floating-point registers in the same format as the general-purpose registers:
| Numeric | o32 | Notes |
|---|---|---|
| $f0 | $fv0 | First return value |
| $f2 | $fv1 | Second return value |
| $f4 | $ft0 | Temporary |
| $f6 | $ft1 | " |
| $f8 | $ft2 | " |
| $f10 | $ft3 | " |
| $f12 | $fa0 | Argument |
| $f14 | $fa1 | " |
| $f16 | $ft4 | Temporary |
| $f18 | $ft5 | " |
| $f20 | $fs0 | Saved |
| $f22 | $fs1 | " |
| $f24 | $fs2 | " |
| $f26 | $fs3 | " |
| $f28 | $fs4 | " |
| $f30 | $fs5 | " |
and their corresponding odd ones by suffixing an f, i.e. $f1 = $fv0f etc.
It's simple to do and makes that damn float regalloc a lot easier to understand (if not fix).
You might also ask yourself if you'd put up with this for the GPRs: just because the default is to use the ABI names for the GPRs and numeric for the FPRs, doesn't mean it's sensible.
The good news is that almost all of the common Nintendo 64 tools have already been made compatible:
- mips_to_c
- asm-differ
- asm-processor
- decomp-permuter
- decomp.me
- spimdisasm
- splat
The following still need support to be added:
- debuggers
- old assemblers
There is also the possibility that GAS (i.e. the GNU assembler) may support them in future, although it is a long and winding road to get anything accepted to GCC.
You will need to update this file to include the aliases, since no assembler seems to recognise the ABI names. It is sufficient to add
.set $fv0, $f0
.set $fv0f, $f1
.set $fv1, $f2
.set $fv1f, $f3
.set $ft0, $f4
.set $ft0f, $f5
.set $ft1, $f6
.set $ft1f, $f7
.set $ft2, $f8
.set $ft2f, $f9
.set $ft3, $f10
.set $ft3f, $f11
.set $fa0, $f12
.set $fa0f, $f13
.set $fa1, $f14
.set $fa1f, $f15
.set $ft4, $f16
.set $ft4f, $f17
.set $ft5, $f18
.set $ft5f, $f19
.set $fs0, $f20
.set $fs0f, $f21
.set $fs1, $f22
.set $fs1f, $f23
.set $fs2, $f24
.set $fs2f, $f25
.set $fs3, $f26
.set $fs3f, $f27
.set $fs4, $f28
.set $fs4f, $f29
.set $fs5, $f30
.set $fs5f, $f31
to the bottom.
If you are using transient assembly files, you are essentially on your own implementing the names in your disassembler.
If you have assembly in the repo, you can just run this python script on the assembly folders to swap over.
As of 28 March (commit d74ba87), has automatic support, will automatically use the names from the assembly file to name the phis and temps, and can take ABI names as --reg-vars.
As of 23 February (commit 07e617e) has support, but needs an extra objdump option: add
config['objdump_flags'] = ['-M','reg-names=32']to diff_settings.py to output the ABI names.
As of 27 March (commit fd28ec1), has automatic support; if you are not subrepoing/submoduling it, you will need to copy across the new prelude.inc or include your project-wide one.
Supports as of 30 March (commit 85644d6). No special changes are needed apart from updating.
As of the 5 April 2022 update, supports input, diff display with them, and output them with its copy of mips_to_c. This can be enabled under "Scratch options":

They can also be added as the default output for a project.
As of 20 May 2022 (commit ae5dc3f). Support as the command-line options --Mfpr-names o32 or --Mreg-names o32
Supported as of version 0.9.0, via mips_abi_float_regs: o32 as a top-level option in the yaml.
If you use this directly e.g. for troubleshooting, you can get it to output the ABI names with the
-Mreg-names=32
flag.
If your project uses a different ABI, the float registers are different! Currently we have only worked on implementing the o32 ABI's names, since that is the one the Nintendo 64 uses, and most of the projects we are involved in are Nintendo 64.
Essentially an ABI is a set of rules to the compiler for how to construct the assembly, including
- width of the registers
- which registers to treat as saved/temporary (i.e. which it can consider preserved by function calls and which it has to save manually)
- which registers are used for arguments/returns
- how to treat the stack
etc. The processor is not aware of the ABI itself but it may have flags that need to be set for it to comply with the rules of the ABI (such as one that tells it to use 32-bit registers, for example). (It is also useful to note that the ABI can only change so much: it cannot change how the RA register works, for example.)
There are three ABIs available for Nintendo 64-era MIPS:
- o32: 32-bit wide registers, must reserve stack for arguments, 4 argument registers, even float registers are used except for doubles, which use pairs.
- n32: 64-bit wide registers, 32-bit pointers, registers completely rearranged (e.g. 8 argument registers available).
- n64: everything 64-bit, float registers different again.
Most importantly, all Nintendo 64 code is compiled for the o32 ABI, because libultra was only distributed compiled with o32 ABI, and code from different ABIs cannot be mixed (due to the differing calling conventions).
Because the registers change categories between ABIs, there are different mappings from the numeric values to the register names, which objdump offers with the -Mreg-names option (as well as the more restrictive -Mgpr-names and -Mfpr-names options, which can take one of numeric, 32, n32, 64. For tables of the different register uses/names in different ABIs, you can consult MIPS registers.
For some reason, the general-purpose registers get their ABI names by default in objdump (and these names are accepted by the GNU assembler), but the floating-point registers do not. Yet, anyway.
