3

Do we need to initialize registers to 0 when writing functions in assembly language ?

Just to make sure that there aren't values in those registers from previous programs.

smac89
  • 39,374
  • 15
  • 132
  • 179
maddie
  • 1,854
  • 4
  • 30
  • 66
  • 1
    Not to get all tautological, but only values that matter, matter. So any question about "do I need to initialize this" inherently depends on context. – harold Nov 25 '16 at 20:39

2 Answers2

4

In general, probably yes.

Register state at program start depends on the operating system. For instance, if you are running on an OS that honors the ELF psABI for MIPS, then registers $2, $29, and $31 have meaningful values at program start, and the others contain unspecified values - see the "Process Initialization" section.

You sound like you might be confused about the difference between a program and a function. What a function can expect upon entry is also documented in the psABI - see the "Function Calling Sequence" section in the same document - the short version being that $4-$7, $25, $28, $29, $31, $f12, $f13, $f14, and $f15 may contain useful values, all the others are unspecified, and you're obliged to make sure that the values you find in $16-$23, $28, $29, $30, and $f20-$f31 on entry are unchanged on exit (i.e. if you change them you must save the old values and restore them before exiting; conversely, if you call a function yourself, you have to assume that it has overwritten all the other registers with unspecified values before returning).

If you're using an OS that does not honor the ELF psABI, then you need to find the equivalent document for your OS. There will be some specification, somewhere. Conceivably you will have to reverse engineer a compiler to get it, though.

zwol
  • 135,547
  • 38
  • 252
  • 361
3

No, you don't need to zero them, but you do need to assume that every register holds random garbage on entry to your function / program, except for registers that hold inputs (e.g. function args) or that the calling convention requires to have some kind of useful value (e.g. the stack pointer).

Normally that's fine; you don't need to clear out the random garbage "early". If your first use of a register is writing something to it, you don't need to write a zero and then write whatever you actually want to put there.

However, if you're going to use it as a counter (i.e. increment it in a loop), then yes you need to zero it ahead of the loop. Same goes for any other use of a register as a source operand, not just a destination operand.


Note that this is one of the few things in assembly language that is the same across every kind of assembly language for every architecture.

Some architectures (like ) have instructions with implicit inputs (like DIV), but zeroing EDX before DIV is still just a case of zeroing registers before reading them.


To put it another way: Every register always has a value, so there isn't anything special about the first use of a register as a write-only operand.

You just have to make sure that the correctness of your code doesn't depend on the contents of any registers or memory that aren't required to have any specific value.


Bonus reading:

For performance, there are rare exceptions to this rule: In some micro-architectures, not all write-only operations are the same. Some actually have a false-dependency on the old value of the output! Using a cheap dependency-breaking way to write the register lets out-of-order execution avoid waiting for an "input" that it doesn't need, in case your code runs after something that might use that register at the end of a long dependency chain (e.g. involving cache misses).

I don't know of any MIPS examples (hopefully there aren't any), so I'll use x86 as an example:

For int->float instructions that put their result in the low element of a vector register (e.g. CVTSI2SS), Intel took the short-sighted approach of designing it to merge into the destination vector, instead of zeroing the rest of the destination. The first-gen CPUs to have SSE were Intel Pentium III, which only had 64-bit vector execution units. I think zeroing the upper 64-bits of a vector would have cost it an extra uop, or at least internally required the convert instruction to produce both halves of the vector as output, instead of just the modified low half.

The vast majority of use-cases for int->float conversion just want the float as a scalar, not inserted into another vector, so the dependency on the destination register is potentially harmful. gcc avoids it with an extra xor-zeroing instruction to zero the destination XMM register before running CVTSI2SS. This extra zeroing instruction is not totally free even on the latest CPUs: it takes code-size and front-end bandwidth. So it's a usually-very-small extra cost for every int->float conversion to avoid rare but potentially-large slowdowns in unpredictable cases when two otherwise-independent dependency chains are linked by input-dependencies on the same register.

Also, the destination register of POPCNT/LZCNT/TZCNT is architecturally write-only, but Intel's implementation has a false dependency on the output register. So it does actually make sense to zero the destination register before running POPCNT, if necessary to avoid unexpectedly creating a loop-carried dependency chain.

See an example of gcc inserting an extra xor-zeroing instruction ahead of popcnt and int->float conversion on the Godbolt Compiler Explorer.

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847