Procedures and the Stack
Laboratory Manual
The lab manual contains the full theory, circuit diagrams, and requirements for this experiment - keep it open while you work. Download Experiment 4 PDF
Examples
Example 1: Nested Uppercase Counter
This example demonstrates a nested call: CountUpperNested(ptr) scans a null-terminated string and calls IsUpper(ch) for each character. It shows saving/restoring LR and using callee-saved registers (R4 for the running count, R5 for the scan pointer). Both values must survive the BL IsUpper call, so under the AAPCS they cannot be kept in caller-saved registers like R0–R3. Compare it with Example 2 of Experiment 3, which counts the same letters inline with no procedures.
AREA RESET, CODE, READONLY
EXPORT __Vectors
__Vectors
DCD 0x20001000 ; Initial SP
DCD Reset_Handler ; Reset vector
ALIGN
AREA MYCODE, CODE, READONLY
ENTRY
EXPORT Reset_Handler
IsUpper ; IsUpper(R0 = ch) -> R0 = 1 if 'A'..'Z', else 0
CMP R0, #'A'
BLT not_upper
CMP R0, #'Z'
BGT not_upper
MOV R0, #1
BX LR
not_upper
MOV R0, #0
BX LR
CountUpperNested ; CountUpperNested(R0 = ptr) -> R0 = Upper Count
PUSH {R4, R5, LR} ; save callee-saved regs + return address
MOV R5, R0 ; R5 = ptr (callee-saved: must survive the BL below)
MOV R4, #0 ; R4 = count
cu_next
LDRB R0, [R5], #1 ; R0 = *ptr++; post-increment pointer in R5
CBZ R0, cu_done ; if null terminator, finish
BL IsUpper ; R0 = 0/1 based on 'A'..'Z' (R0-R3 are caller-saved: may be clobbered)
ADD R4, R4, R0 ; count += result
B cu_next
cu_done
MOV R0, R4 ; return count in R0
POP {R4, R5, PC}
Reset_Handler
LDR R0, =mystring
BL CountUpperNested
LDR R2, =UPPERCOUNT
STR R0, [R2]
STOP B STOP
AREA CONSTANTS, DATA, READONLY
mystring DCB "Hello ARM World!", 0 ; Uppercase: H, A, R, M, W -> 5
AREA MYDATA, DATA, READWRITE
UPPERCOUNT SPACE 4 ; should be 5
ENDCheck: Verify that UPPERCOUNT contains 5 for the test string.
Example 2: Integer to Decimal String with the Stack
UIntToStr(value, buf) converts an unsigned integer into a null-terminated decimal ASCII string and returns the number of digits in R0. It finds each digit by dividing by 10 (UDIV, then MLS for the remainder), and this produces the digits in reverse order, least-significant first. Pushing each digit and then popping them all back reverses that order again, because the stack is last-in, first-out. Each PUSH {R12} takes a full 4-byte word even though it holds only one character. Because every push is matched by a pop, SP returns to its starting value before the final POP {R4, PC}.
AREA RESET, CODE, READONLY
EXPORT __Vectors
__Vectors
DCD 0x20001000 ; Initial SP
DCD Reset_Handler ; Reset vector
ALIGN
AREA MYCODE, CODE, READONLY
ENTRY
EXPORT Reset_Handler
UIntToStr ; UIntToStr(R0 = value, R1 = buf) -> R0 = digit count
PUSH {R4, LR} ; save callee-saved R4 and the return address
MOV R2, #0 ; R2 = digit count
MOV R3, #10 ; R3 = divisor
its_div
UDIV R4, R0, R3 ; R4 = value / 10
MLS R12, R4, R3, R0 ; R12 = value - (value / 10) * 10 = value % 10
ADD R12, R12, #'0' ; digit -> ASCII character
PUSH {R12} ; digits are produced least-significant first...
ADD R2, R2, #1 ; count++
MOV R0, R4 ; value = value / 10
CMP R0, #0
BNE its_div ; repeat until value is 0 (value 0 still gives "0")
MOV R0, R2 ; return value = digit count
its_pop
POP {R12} ; ...so popping returns them most-significant first
STRB R12, [R1], #1 ; *buf++ = character
SUBS R2, R2, #1 ; count--
BNE its_pop
MOV R12, #0
STRB R12, [R1] ; null terminator
POP {R4, PC} ; restore R4 and return (SP is back where it started)
Reset_Handler
LDR R0, =40213 ; value to convert
LDR R1, =NUMSTR ; destination buffer in RAM
BL UIntToStr ; R0 = 5, NUMSTR = "40213"
STOP B STOP
AREA MYDATA, DATA, READWRITE
NUMSTR SPACE 12 ; up to 10 digits + null (written before it is read)
ENDCheck: Verify that R0 = 5 after the call, and open a Memory window at NUMSTR to confirm the bytes 34 30 32 31 33 00 ("40213"). Single-step the first loop with the Registers window open and watch SP drop by 4 for each digit. Then confirm that it climbs back during the second loop. Try the value 0 as well; the result should be "0".
Tasks
Task 1: Count Vowels with Procedures
Rewrite Task 1 of Experiment 3 as procedures:
- Create a procedure
IsVowelthat takes a character in R0 and returns 1 in R0 if it is a vowel (a, e, i, o, u, upper- or lowercase), and 0 otherwise. - Create a procedure
CountVowelsthat takes a string pointer in R0 and returns the number of vowels in R0, callingIsVowelfor each character. - Follow AAPCS conventions for parameter passing and register usage.
- Test it with the Experiment 3 string; the result must match your earlier answer.
Task 2: Factorial Calculation (Recursive)
Note on recursion
Example 1's nested call has one procedure calling a different one. In recursion, FactorialRec calls itself. Each call needs its own copy of LR (and of any register it modifies) preserved across the inner call. So every invocation must PUSH its own LR, and usually the current n or partial result, before recursing, then POP them back on the way out. Because each active call keeps its own saved values on the stack, the recursion unwinds in reverse order as each POP/BX LR returns to its caller.
Implement a recursive procedure to calculate the factorial of a non-negative integer:
- Create a procedure
FactorialRecthat takes a non-negative integer in R0 and returns its factorial in R0. - Use recursion to compute the factorial, with a base case that handles 0! = 1.
- Manage the stack appropriately to save and restore registers as needed.
- Follow AAPCS conventions for parameter passing and register usage.
- Test the procedure with several inputs (including 0 and 1) to verify correctness.