Preserving callee-saved registers in ARM Architecture - Time & Space Complexity
When preserving callee-saved registers, we want to understand how the work grows as the number of registers or instructions increases.
We ask: How does saving and restoring registers affect execution time as the code changes?
Analyze the time complexity of the following ARM assembly snippet that saves and restores callee-saved registers.
push {r4-r7, lr} // Save registers on stack
... // Function body instructions
pop {r4-r7, pc} // Restore registers and return
This code saves four registers before the function runs and restores them before returning.
Look for repeated actions that take time.
- Primary operation: Saving and restoring each callee-saved register to/from the stack.
- How many times: Once per function call, for each register saved.
As the number of callee-saved registers increases, the time to save and restore grows linearly.
| Number of Registers (n) | Approx. Operations |
|---|---|
| 4 | 8 (4 push + 4 pop) |
| 8 | 16 (8 push + 8 pop) |
| 16 | 32 (16 push + 16 pop) |
Pattern observation: Doubling registers doubles the save and restore operations.
Time Complexity: O(n)
This means the time to preserve registers grows directly with how many registers you save and restore.
[X] Wrong: "Saving registers takes constant time no matter how many are saved."
[OK] Correct: Each register requires a separate save and restore step, so more registers mean more work.
Understanding how saving registers scales helps you write efficient low-level code and shows you grasp how function calls impact performance.
What if we used a different calling convention that saves fewer registers? How would the time complexity change?
Practice
Solution
Step 1: Understand the role of callee-saved registers
Callee-saved registers must keep their values unchanged after a function call to avoid breaking the caller's data.Step 2: Identify the purpose of preserving these registers
Preserving means saving and restoring their values so the caller sees no change.Final Answer:
To keep the values intact across function calls -> Option CQuick Check:
Preserving registers = keeping values intact [OK]
- Thinking callee-saved registers speed up execution
- Confusing callee-saved with caller-saved registers
- Believing registers can be freely modified without saving
Solution
Step 1: Identify instruction to save registers
The PUSH instruction stores registers onto the stack, preserving their values.Step 2: Confirm PUSH is used at function start
Functions use PUSH to save callee-saved registers before modifying them.Final Answer:
PUSH -> Option DQuick Check:
Save registers = PUSH [OK]
- Using MOV which copies but doesn't save to stack
- Using ADD which changes values, not saves
- Using CMP which compares values, not saves
func:
PUSH {r4, r5}
MOV r4, #10
MOV r5, #20
POP {r4, r5}
BX lr
What will be the value of registers r4 and r5 after returning to the caller?Solution
Step 1: Analyze PUSH and POP effect
PUSH saves original r4 and r5 values on stack; POP restores them before return.Step 2: Understand MOV instructions inside function
Inside function, r4 and r5 are changed, but POP restores original values before returning.Final Answer:
r4 and r5 unchanged from caller -> Option AQuick Check:
Saved and restored registers = unchanged after call [OK]
- Assuming MOV changes persist after POP
- Ignoring that POP restores saved registers
- Thinking registers reset to zero automatically
Solution
Step 1: Understand callee-saved register role
These registers must be restored to keep caller's data intact.Step 2: Consequence of not restoring
If not restored, caller sees wrong values, causing crashes or wrong behavior.Final Answer:
The program may crash or behave unpredictably -> Option AQuick Check:
Not restoring callee-saved = crashes/unpredictable [OK]
- Thinking it improves speed
- Believing caller's registers stay safe without restore
- Ignoring stack pointer effects
Solution
Step 1: Identify correct saving method
Registers must be pushed together at function start to save their original values.Step 2: Identify correct restoring method
Registers must be popped in the same order before returning to restore original values.Step 3: Check options for correct sequence
PUSH {r4, r5, r6} ...function body... POP {r4, r5, r6} pushes all three registers at once and pops them before return, which is correct.Final Answer:
PUSH {r4, r5, r6} ...function body... POP {r4, r5, r6} -> Option BQuick Check:
Push all then pop all = correct preservation [OK]
- Pushing and popping registers separately in wrong order
- Using POP before PUSH
- Overwriting registers without saving
Ready to prove it?
Pass the test and this pattern moves unknown
