In the previous video, we have talked about how to pass arguments to a subroutine
and how to return the result from a subroutine. In this video, we will talk about another important
contract between a caller and a callee: which registers should a subroutine preserve?
The procedure call standard serves as a contract between a caller and a callee. If both the caller
and the callee follows the standard, they can be separately written, separately compiled,
and separately assembled but work together. It allows a C function to call an assembly
subroutine, and vice versa. This video will talk about what obligation a subroutine has in terms of
protecting the caller s running environment. Specifically, which registers should a subroutine
preserve during a function call. For Arm Procedure Call Standard, the caller expects that
the values of these registers except r9 and r12 should not be modified by the subroutine.
If a subroutine needs to modify any of these registers to hold local variables,
the subroutine must save the original value of the register onto the stack before modifying it,
and pop its value off the stack to recover the original value before the subroutine returns.
The role of register r9 is platform dependent, and typically it is not required to preserve its
value. The caller does not expect the subroutine to preserve the first four registers, r0 to r3,
and register r12. If the caller wish that these registers remain unchanged,
the caller should push these registers onto the stack before it calls the subroutine.
Let s look at this example. In the caller program,
suppose register r4 holds a value that will be used later after the subroutine foo is completed.
The caller program expects that the subroutine foo() does not modify
r4. After the subroutine foo is completed, r4 should still hold the value 100.
Suppose the subroutine needs to use register r4 to hold a new value for computation.
At the very beginning of the subroutine, r4 should be pushed onto the stack. Thus,
the original value of r4 is saved in the stack. After that, the subroutine can freely modify r4.
Before calling BX LR to exit the subroutine, the subroutine uses the POP instruction to recover r4
to its original value. The paired PUSH and POP operations preserve the value of r4.
Another way to exit from a subroutine is to use PUSH LR and POP PC, rather than executing BX LR.
The link register is pushed onto the stack at the beginning,
but its value can be directly popped into the program counter. This copies the original
value of the link register to the program counter. Here is a Quiz. The subroutine foo
calls another subroutine named bar. Pause the video now and find out what is wrong in foo?
Here is the problem. The foo subroutine modifies the link register when it calls bar. However,
the foo subroutine did not preserve the link register. Therefore, when it executes BX LR , it
generates a dead loop because the link register points to the instruction immediately after the
instruction BL bar . As results, the foo function cannot return the control back to the caller.
Here is one solution to fix the problem. The subroutine should preserve the link register
on the stack, and recover LR s original value before it calls BX LR . Here is
another solution. Rather than recovering LR s original value,
we can pop LR s original value directly to the program counter PC. In the previous examples,
we have talked about using the stack to preserve registers on the stack. Here is a quick review
about the stack. Arm Cortex-M uses full descending stack. The stack grows toward the lower memory
addresses. The Stack Pointer decreases as the value of a register is pushed onto the stack.
On the other hand, the stack pointer increases as a value is popped off the stack. It is called a
full stack because the stack pointer always points to a valid value, instead of an empty spot.
Pay attention here. For both PUSH and POP instructions, the order specified in the list
does not matter. For example, suppose we want to push these four registers onto the stack.
Here is the result. No matter what order these registers are specified in the list,
the largest-numbered register is always pushed first. In this example, r7 is pushed first,
then r3, r2, and r1. In the pop instruction, the top of the stack is popped to the smallest
numbered registered first. Suppose we run this POP instruction to pop four value off the stack.
Here is the result after the pop instruction completes. The top of the stack is always popped
to the smallest numbered register first, no matter what order these registers are specified in the
POP instruction. Therefore, r1 gets the first value, then r2 gets the second value, next r3,
and the last r7. In summary, the largest-numbered register is pushed first but popped last.
Since the order in which registers are specified in the PUSH and POP instructions does not matter,
the following four programs are equivalent to each other. Here is the summary of the convention of
preserving registers. The first four registers are used to pass arguments to a subroutine
and receive the result from the subroutine. The subroutine does not need to preserve these values.
However, the subroutine is required to preserve the values of those registers except r9 and r12.
If the subroutine needs to write a value to any of these registers, the subroutine must
push the register onto the stack before making any modification, and then recover
its original value before returning the control to the caller. Thank you for watching.