The zero-register on RISC CPUs is useful for two reasons:
It's a useful constant
Depending on restrictions of the ISA, you can't use a literal in some instructions encoding, but you can be sure you can use that r0
to get 0.
It can be used to synthesize other instructions
This is perhaps the most important point. As a ISA designer, you can trade-off a general purpose register to a zero-register to be able to synthesize other useful instructions. Synthesizing instructions is good because by having less actual instructions, you need less bits to encode an operation in a opcode, which frees-up space in the instruction encoding space. You can use that space to have e.g. bigger address offsets and/or literals.
The semantics of the zero-register is like /dev/zero
on *nix systems: everything written to it is discarded, and you always read back 0.
Let's see a few examples of how we can make pseudo-instructions with the help of the r0
zero-register:
; ### Hypothetical CPU ###
; Assembler with syntax:
; op rd, rm, rn
; => rd: destination, rm: 1st operand, rn: 2nd operand
; literal as #lit
; On an CPU architecture with a status register (which contains arithmetic status
; flags), `sub` can be used, with r0 as destination to discard result.
cmp rn, rm ; => sub r0, rn, rm
; `add` instruction can be used as a `mov` instruction:
mov rd, rm ; => add rd, rm, r0
mov rd, #lit ; => add rd, r0, #lit
; Negate:
neg rd, rm ; => sub rd, r0, rm
; On CPU without status flags,
nop ; => add r0, r0, r0
; RISC-V's `jal` instruction -- Jump and Link: Jump to PC-relative instruction,
; save return address into rd; we can synthesize a `jmp` instruction out of it.
jmp dest ; => jal r0, dest
; You can even load from an absolute (direct) address, for a usually small range
; of addresses by using a literal offset as an address.
ld rd, addr ; => ld rd, [r0, #addr]
The case of MIPS
I looked more closely at the MIPS instruction set. There are a handful of pseudo-instructions that uses $zero
; they are mainly used for branches. Here are some examples of what I've found:
move $rt, $rs => add $rt, $rs, $zero
not $rt, $rs => nor $rt, $rs, $zero
b Label => beq $zero, $zero, Label ; a small relative branch
bgt $rs, $rt, Label => slt $at, $rt, $rs
bne $at, $zero, Label
blt $rs, $rt, Label => slt $at, $rs, $rt
bne $at, $zero, Label
bge $rs, $rt, Label => slt $at, $rs, $rt
beq $at, $zero, Label
ble $rs, $rt, Label => slt $at, $rt, $rs
beq $at, $zero, Label
As for why you have found only one instance of the $zero
register in your disassembly, perhaps it's your disassembler that is smart enough to transform known sequences of instructions into their equivalent pseudo-instruction.
Is the zero-register really useful?
Well, apparently, ARM finds having a zero-register useful enough that in their (somewhat) new ARMv8-A core, which implement AArch64, there's now a zero-register in 64-bit mode; there wasn't a zero-register before. (The register is a bit special though, in some encoding contexts it's a zero-register, in others it instead designates the stack pointer)
Best Answer
No. You're confusing the stack pointer with the program counter. The PC is incremented after every instruction.
To make room on the stack for the local variable
f
.None. It's redundant. An optimization flag would probably have removed that.