0
0
ARM Architectureknowledge~15 mins

Preserving callee-saved registers in ARM Architecture - Deep Dive

Choose your learning style9 modes available
Overview - Preserving callee-saved registers
What is it?
Preserving callee-saved registers means saving certain CPU registers before a function uses them and restoring them before the function ends. These registers hold important data that must not be lost when one function calls another. The ARM architecture defines which registers are callee-saved, meaning the called function is responsible for keeping their values intact. This ensures that the calling function can continue correctly after the call.
Why it matters
Without preserving callee-saved registers, data stored in these registers could be overwritten by called functions, causing incorrect program behavior or crashes. This preservation allows multiple functions to work together safely, sharing the CPU without losing important information. It is essential for reliable software, especially in complex systems like operating systems or embedded devices.
Where it fits
Before learning this, you should understand basic ARM CPU registers and how function calls work. After this, you can learn about calling conventions, stack management, and how compilers generate code for function calls.
Mental Model
Core Idea
Callee-saved registers are like borrowed tools that a function must return in the same condition before it finishes.
Think of it like...
Imagine lending a friend your favorite pen to use temporarily. They must give it back exactly as they received it so you can keep writing without interruption.
┌───────────────────────────────┐
│ Caller function starts         │
│ Registers hold important data  │
├───────────────────────────────┤
│ Calls callee function          │
│ ┌───────────────────────────┐ │
│ │ Callee saves registers     │ │
│ │ Uses registers for work    │ │
│ │ Restores registers         │ │
│ └───────────────────────────┘ │
│ Returns to caller             │
│ Registers unchanged           │
└───────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding CPU Registers
🤔
Concept: Registers are small storage locations inside the CPU used to hold data temporarily during program execution.
In ARM CPUs, registers are like fast-access boxes that hold numbers or addresses. They help the CPU do calculations and keep track of where it is in a program. Some registers are general-purpose, while others have special roles.
Result
You know what registers are and why they are important for running programs.
Understanding registers is crucial because all function calls and data manipulations depend on them.
2
FoundationBasics of Function Calls
🤔
Concept: A function call temporarily transfers control to another set of instructions, possibly using registers to pass data.
When a program runs a function, it jumps to that function's code. The function may use registers to receive inputs and return results. After finishing, it returns control to the original place in the program.
Result
You understand how functions start and end, and how data moves between them.
Knowing function calls helps you see why preserving data in registers matters during these jumps.
3
IntermediateCaller vs Callee Responsibilities
🤔Before reading on: do you think the caller or callee is responsible for saving callee-saved registers? Commit to your answer.
Concept: Registers are divided into caller-saved and callee-saved, defining who must save and restore them during calls.
Caller-saved registers can be overwritten by the called function, so the caller must save them if needed. Callee-saved registers must be preserved by the called function, meaning it saves their values at the start and restores them before returning.
Result
You can identify which registers each function must protect.
Understanding this division prevents data loss and confusion about who handles which registers.
4
IntermediateWhich Registers Are Callee-Saved in ARM
🤔
Concept: ARM architecture defines specific registers as callee-saved, typically r4 to r11 and the stack pointer.
In ARM, registers r4 through r11 are callee-saved. This means any function that uses these registers must save their original values (usually on the stack) and restore them before returning. The stack pointer (sp) is also preserved to keep the call stack intact.
Result
You know exactly which registers need saving in ARM functions.
Knowing the exact registers helps write or understand correct assembly and prevents subtle bugs.
5
IntermediateHow Registers Are Preserved Using the Stack
🤔
Concept: Functions save callee-saved registers by pushing them onto the stack and pop them back before returning.
The stack is a special memory area that grows and shrinks as functions call each other. To preserve registers, a function pushes their values onto the stack at the start. Before returning, it pops these values back into the registers, restoring their original state.
Result
You understand the practical method of saving and restoring registers.
Using the stack for preservation ensures that multiple nested calls do not overwrite each other's data.
6
AdvancedCompiler Role in Register Preservation
🤔Before reading on: do you think compilers always save all callee-saved registers or only those used? Commit to your answer.
Concept: Compilers automatically generate code to save and restore callee-saved registers following calling conventions.
When you write code in a high-level language, the compiler inserts instructions to save callee-saved registers if your function uses them. This automatic handling follows the ARM calling convention, ensuring interoperability between different code modules.
Result
You realize compilers optimize preservation to save only necessary registers.
Knowing compiler behavior helps debug assembly and optimize performance.
7
ExpertPitfalls and Optimizations in Register Preservation
🤔Before reading on: do you think skipping saving a callee-saved register can ever be safe? Commit to your answer.
Concept: Advanced techniques can reduce overhead by minimizing saved registers, but mistakes can cause crashes or data corruption.
Some compilers analyze which callee-saved registers a function actually uses and save only those. However, if a function mistakenly fails to save a used callee-saved register, it can overwrite data the caller expects to remain unchanged, causing bugs. Expert programmers must understand these details when writing assembly or optimizing critical code.
Result
You appreciate the balance between efficiency and correctness in register preservation.
Understanding these subtleties prevents hard-to-find bugs and improves low-level code quality.
Under the Hood
When a function is called, the CPU switches execution to the callee. The callee-saved registers are part of the CPU's register file. To preserve them, the callee pushes their current values onto the stack memory, which is a reserved area in RAM. This stack grows downward in memory. After the callee finishes its work, it pops the saved values back into the registers before returning control. This push/pop sequence ensures the caller sees the registers unchanged.
Why designed this way?
This design balances efficiency and safety. By making the callee responsible for saving only certain registers, the calling function can assume those registers remain stable, simplifying code generation. Alternatives like saving all registers on every call would waste time and memory. The ARM calling convention evolved to optimize performance on limited hardware while maintaining program correctness.
Caller Function
  │
  ▼
┌─────────────────────┐
│ Call Callee Function │
└─────────────────────┘
          │
          ▼
┌─────────────────────────────┐
│ Callee Saves r4-r11 on stack│
│ Uses registers for work      │
│ Restores r4-r11 from stack  │
└─────────────────────────────┘
          │
          ▼
┌─────────────────────┐
│ Return to Caller     │
│ Registers intact     │
└─────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do callee-saved registers get saved by the caller or callee? Commit to your answer.
Common Belief:Callee-saved registers are saved by the caller before calling a function.
Tap to reveal reality
Reality:Callee-saved registers are saved and restored by the callee function itself, not the caller.
Why it matters:If a caller assumes it must save these registers, it wastes time and memory. If a callee fails to save them, the caller's data is corrupted.
Quick: Are all registers preserved automatically during function calls? Commit to yes or no.
Common Belief:All CPU registers are automatically preserved during function calls.
Tap to reveal reality
Reality:Only callee-saved registers are guaranteed to be preserved; caller-saved registers can be overwritten by the callee.
Why it matters:Assuming all registers are preserved can cause bugs when caller-saved registers are unexpectedly changed.
Quick: Can skipping saving unused callee-saved registers cause problems? Commit to yes or no.
Common Belief:It's safe to skip saving any callee-saved register if the function doesn't use it.
Tap to reveal reality
Reality:Yes, skipping saving unused callee-saved registers is safe; only registers actually used by the function need saving.
Why it matters:Understanding this allows compilers to optimize code by saving only necessary registers, improving performance.
Quick: Does the stack pointer need to be preserved like other callee-saved registers? Commit to yes or no.
Common Belief:The stack pointer does not need to be preserved because it is managed automatically.
Tap to reveal reality
Reality:The stack pointer is treated specially and must be preserved to maintain correct stack structure during calls.
Why it matters:Failing to preserve the stack pointer can corrupt the call stack, causing crashes or unpredictable behavior.
Expert Zone
1
Some ARM variants and calling conventions may treat additional registers as callee-saved, requiring careful attention when writing assembly.
2
Tail call optimizations can eliminate the need to restore callee-saved registers by reusing the caller's stack frame, but this requires precise control flow.
3
In interrupt or exception handlers, special rules apply for saving registers, often requiring saving more than just callee-saved registers.
When NOT to use
Preserving callee-saved registers is not applicable in leaf functions that do not call others and do not use these registers; in such cases, saving/restoring is unnecessary. For performance-critical code, sometimes manual register management or using caller-saved registers is preferred. Alternatives include using caller-saved registers or special calling conventions for specific scenarios.
Production Patterns
In real-world ARM software, compilers generate prologues and epilogues that save and restore callee-saved registers automatically. Hand-written assembly functions carefully save only the registers they use to minimize overhead. Operating system kernels and embedded firmware often follow strict conventions to preserve registers across system calls and interrupts.
Connections
Calling Conventions
Preserving callee-saved registers is a key part of calling conventions that define how functions interact at the machine level.
Understanding register preservation clarifies how calling conventions ensure smooth cooperation between different code modules and languages.
Stack Memory Management
Register preservation relies on the stack to save and restore register values during function calls.
Knowing how the stack works helps understand the mechanics and importance of saving registers safely.
Theater Stage Props Management
Both involve temporarily borrowing and returning important items to keep the performance smooth.
Recognizing this similarity highlights the universal principle of preserving state during transitions in complex systems.
Common Pitfalls
#1Not saving callee-saved registers before modifying them in a function.
Wrong approach:my_function: mov r4, #10 mov r5, #20 bx lr
Correct approach:my_function: push {r4, r5} mov r4, #10 mov r5, #20 pop {r4, r5} bx lr
Root cause:Misunderstanding that callee-saved registers must be preserved leads to overwriting caller's data.
#2Saving all registers regardless of usage, causing unnecessary overhead.
Wrong approach:my_function: push {r4-r11} ... pop {r4-r11} bx lr
Correct approach:my_function: push {r4, r5} ... pop {r4, r5} bx lr
Root cause:Not analyzing which registers are actually used leads to inefficient code.
#3Modifying the stack pointer without restoring it properly.
Wrong approach:my_function: sub sp, sp, #16 mov r4, #5 add sp, sp, #8 bx lr
Correct approach:my_function: sub sp, sp, #16 mov r4, #5 add sp, sp, #16 bx lr
Root cause:Incorrect stack pointer adjustment corrupts the stack frame, causing crashes.
Key Takeaways
Callee-saved registers are those a called function must preserve to keep the caller's data intact.
In ARM architecture, registers r4 to r11 are callee-saved and must be saved and restored by the callee if used.
Preserving these registers is done by pushing them onto the stack at function start and popping them before returning.
Compilers automate this process, but understanding it is essential for writing and debugging low-level code.
Failing to preserve callee-saved registers correctly leads to subtle bugs and program crashes.