Recursive function in assembly in ARM Architecture - Time & Space Complexity
When we look at recursive functions in assembly, we want to understand how the number of steps grows as the input changes.
We ask: How many times does the function call itself and what does that mean for performance?
Analyze the time complexity of the following ARM assembly recursive function.
factorial:
cmp r0, #1
ble end_factorial
push {lr}
mov r1, r0
sub r0, r0, #1
bl factorial
pop {lr}
mul r0, r0, r1
end_factorial:
bx lr
This code calculates the factorial of a number by calling itself with a smaller number until it reaches 1.
Look for repeated actions in the code.
- Primary operation: The function calls itself recursively.
- How many times: It calls itself once for each number down to 1.
Each time the input number increases, the function calls itself more times.
| Input Size (n) | Approx. Operations |
|---|---|
| 5 | 5 calls |
| 10 | 10 calls |
| 20 | 20 calls |
Pattern observation: The number of calls grows directly with the input number.
Time Complexity: O(n)
This means the time to finish grows in a straight line as the input number gets bigger.
[X] Wrong: "Recursive functions always take much longer than loops."
[OK] Correct: Some recursive functions, like this factorial, take time proportional to input size, just like loops do.
Understanding how recursion affects time helps you explain your code clearly and shows you know how programs behave as inputs grow.
"What if the function called itself twice each time instead of once? How would the time complexity change?"
Practice
Solution
Step 1: Understand recursion concept
A recursive function calls itself to break down a problem into smaller parts.Step 2: Apply to ARM assembly context
In ARM assembly, recursion is used to solve problems by repeated self-calls with updated parameters.Final Answer:
To call itself to solve smaller parts of a problem -> Option BQuick Check:
Recursion means self-calling function [OK]
- Thinking recursion avoids stack usage
- Confusing recursion with iteration
- Assuming recursion always runs faster
Solution
Step 1: Identify return instruction
In ARM assembly,BX LRis used to branch back to the address stored in the link register, effectively returning from a function.Step 2: Confirm usage in recursion
Recursive functions useBX LRto return after completing each call.Final Answer:
BX LR -> Option CQuick Check:
Return uses BX LR instruction [OK]
- Using BL to return instead of call
- Confusing PUSH with return
- Using MOV incorrectly for return
factorial:
CMP R0, #1
BLE end_factorial
PUSH {LR}
SUB R0, R0, #1
BL factorial
POP {LR}
MUL R0, R0, R1
end_factorial:
MOV R1, R0
BX LRWhat will be the value in R1 after calling
factorial with R0 = 3?Solution
Step 1: Trace the recursive factorial
The function saves LR, decrements R0, calls factorial recursively, restores LR, then multiplies R0 by R1.Step 2: Calculate factorial for R0=3
Call factorial(3): R0=3, not base case, PUSH LR, SUB R0=2, BL factorial(2). factorial(2): PUSH LR, SUB R0=1, BL factorial(1). factorial(1): base case, sets R1=1, returns. factorial(2): POP LR, MUL R0=1 * R1=1 =1, MOV R1=1, return. factorial(3): POP LR, MUL R0=2 * R1=1=2, MOV R1=2, return.Step 3: Correction
However, the MUL uses R0 and R1, but R0 was decremented before recursive call, so the multiplication is (n-1) * fact(n-1), which is incorrect for factorial.Step 4: Final value
Tracing carefully, the final R1 will be 2, which is incorrect factorial of 3 (should be 6). But the code as given will produce 2.Final Answer:
2 -> Option D
- Assuming correct factorial computation (expecting 6)
- Confusing R0 and R1 values
- Missing base case return value
factorial:
CMP R0, #1
BLE end_factorial
SUB R0, R0, #1
BL factorial
MUL R0, R0, R1
end_factorial:
MOV R1, R0
BX LRWhat is the main problem?
Solution
Step 1: Check stack usage for recursion
Recursive calls must save LR on stack before calling to preserve return address.Step 2: Identify missing PUSH/POP
This code lacks PUSH {LR} before BL and POP {LR} after, risking corrupted return address.Final Answer:
Missing PUSH and POP of LR causing return address loss -> Option AQuick Check:
Save LR on stack in recursion [OK]
- Ignoring stack save for LR
- Assuming base case is wrong
- Confusing MOV with MOVS
Solution
Step 1: Importance of base cases
Base cases stop recursion to prevent infinite loops, essential for Fibonacci calculation.Step 2: Save LR on stack before recursive calls
Saving LR preserves return addresses during multiple recursive calls, avoiding crashes.Final Answer:
Handle base cases to stop recursion and save LR on stack before recursive calls -> Option AQuick Check:
Base cases + LR save = correct recursion [OK]
- Skipping base cases causing infinite recursion
- Not saving LR causing return errors
- Using jumps instead of BL for calls
Ready to prove it?
Pass the test and this pattern moves unknown
