Subroutines & the Stack
Writing reusable functions with proper register saving.
Callee-Save Convention
When writing a subroutine, you should save any registers you'll modify (except the return value) and restore them before returning. This way the caller's data is preserved.
The standard approach uses the stack — a region of memory managed with R6 as the stack pointer.
The Stack
The LC-3 stack grows downward (toward lower addresses). R6 is the stack pointer by convention.
Push: Decrement R6, then store Pop: Load, then increment R6; Push R0 onto the stack
ADD R6, R6, #-1 ; Make room
STR R0, R6, #0 ; Store R0 at top of stack
; Pop from stack into R0
LDR R0, R6, #0 ; Load top of stack
ADD R6, R6, #1 ; Shrink stackComplete Subroutine Example
Here's a subroutine that multiplies R0 by R1 using repeated addition:
.ORIG x3000
; Initialize stack pointer
LD R6, STACK
; Set up arguments
AND R0, R0, #0
ADD R0, R0, #6 ; R0 = 6
AND R1, R1, #0
ADD R1, R1, #7 ; R1 = 7
; Call MULTIPLY
JSR MULTIPLY
; R0 now contains 42
HALT
;---------------------------
; MULTIPLY: R0 = R0 * R1
; Uses R2 as counter, R3 as accumulator
;---------------------------
MULTIPLY
; Save registers
ADD R6, R6, #-1
STR R7, R6, #0 ; Save R7 (return address)
ADD R6, R6, #-1
STR R2, R6, #0 ; Save R2
ADD R6, R6, #-1
STR R3, R6, #0 ; Save R3
; R3 = 0 (accumulator), R2 = R1 (counter)
AND R3, R3, #0
ADD R2, R1, #0
MUL_LOOP
ADD R2, R2, #0
BRz MUL_DONE
ADD R3, R3, R0 ; accumulator += R0
ADD R2, R2, #-1 ; counter--
BR MUL_LOOP
MUL_DONE
ADD R0, R3, #0 ; Result in R0
; Restore registers
LDR R3, R6, #0
ADD R6, R6, #1
LDR R2, R6, #0
ADD R6, R6, #1
LDR R7, R6, #0
ADD R6, R6, #1
RET
STACK .FILL xFE00
.ENDAlways save and restore R7 in subroutines that call other subroutines (JSR overwrites R7). The stack makes this clean and systematic.
Write a subroutine called ABS that computes the absolute value of R0. If R0 is negative, negate it. If positive or zero, leave it unchanged. The main program should test with R0 = -3 and print the result.