At the time of writing this article, I didn’t know about function prologue and epilogue. Here is an article about it.
So, I’m learning assembly. There’s something I’ve been wondering for quite some time now. I think I’ve figured it out.
We have a finite number of general purpose registers at our disposal when working with assembly code. E.g r0
to r12
in arm.
These registers are used to store variables.
mov r0, #10
mov r1, #10
add r0, r0, r1
The example above that adds two numbers.
The example uses both registers r0
and r1
.
The question or thing that I’ve been struggling with is,
If we only have
r0
andr1
registers, what happens when we make a function call, and both the caller and callee user0
andr1
?
To explain the issue better, consider the following code snippets
void bar()
{
int a = 10; // mov r0, #10
int b = 10; // mov r1, #10
int c = a + b; // add r0, r0, r1
...
}
void foo()
{
int a = 12; // mov r0, #12
int b = 14; // mov r1, #14
...
bar(); // jmp bar
int c = a + b; // add r0, r0, r1
...
}
r0
and r1
are being used by both, bar()
and foo()
.
In foo()
, once the call to bar()
is made, the integrity of the registers r0
and r1
would not be upheld.
When bar()
returns, the values of a
and b
would not be 12
and 14
as foo()
expects.
Solution
This is where the stack comes to the rescue. One of the uses of the stack is to save the values of the registers and restore them later.
In our example, before using a register in bar()
, the value of the register is first pushed to the stack.
Before bar()
returns, the stack is popped and the values of the registers are restored appropriately
Here is what that would look like
.foo:
mov r0, #12
mov r1, #14
branch bar
; Continue execution
add r0, r0, r1
.bar:
; Push r0 and r1 to the stack
push r0
push r1
mov r0, #10
mov r1, #10
add r0, r0, r1
; Pop stack and set r0 and r1 values
pop r1, stack
pop r0, stack
; Return
ret .foo
Parts to pay attention to, since bar
will use r0
and r1
we push their values to the stack.
.bar:
push r0
push r1
...
And once we are done, just before returning, we pop the stack and set the values to the register. Also note the order of popping, the stack is LIFO(last in first out).
.bar:
...
pop r1
pop r0
; Return
ret .foo
pop
,branch
andpush
in this case are made up, but there should be similar/appropriate instructions to handle that in most ISAs.
Here is another question, that I’ve still not figured out.
What happens when we use up all our registers, this time in the same function?
Answer is here