Skip to content

Subroutine Instructions

The LC-3 provides the ability to jump to a subroutine (like calling a function in Java) and return back to the main program

Like in Java, this is helpful when a program needs to execute the same code several times. The subroutine only has to be written and tested once, then can be executed multiple time

Assembly does not have a way to pass parameters into a subroutine, not a way to return a result back to the main program. This behavior can be emulated by using general purpose registers -or- by using memory locations

One additional difference in assembly is that general purpose registers are shared between the main and subroutine code. So when a subroutine changes it may cause issues in the main program is the original value was still needed. There is a way to safeguard this

JSR

Unconditionally jump to a subroutine

asm
.ORIG x3000

    AND R2, R2, #0; Clear R2
    ADD R2, R2, #7; Place 7 in R2
    
    JSR DoubleValue;
    ;R2 now contains 14
    
    JSR DoubleValue;
    ;R2 now contains 28
    
    JSR DoubleValue;
    ;R2 now contains 56
   
Done HALT

; Doubles the value in R2
;   inputs: R2 contains the value to double
;   output: R2 contains the doubled value
DoubleValue
    ADD R2, R2, R2
    RET

;End of Program

;Data Declarations-------------
.END

This subroutine uses registers to pass in parameters and return results

The subroutine us located between HALT and .END

The comment above the subroutine is essential. It documents how the main program uses the subroutine (which registers to use for parameters/input and which to use for a return value/output)

The subroutine is named with a label, DoubleValue in this case. The main program prepares parameters (placing a value in R2), the calls the subroutine with JSR <label>

asm
.ORIG x3000

    AND R2, R2, #0  ; Clear R2
    ADD R2, R2, #7  ; Place 7 in R2
    ST R2, Param    ; Copy value into memory for Sub to use
    
    JSR DoubleValue
    ;Result now contains 14
    
    LD R2, Result   ; Get result into a register
    ST R2, Param    ; Copy result into param
    JSR DoubleValue
    ;Result now contains 28
    
    LD R2, Result   ; Get result into a register
    ST R2, Param    ; Copy result into param
    JSR DoubleValue
    ;Result now contains 56
   
Done HALT

; Doubles the value in Param
;   inputs: Param contains the value to double
;   output: Result contains the doubled value
DoubleValue
    LD R4, Param
    ADD R4, R4, R4
    ST R4, Result
    RET

;End of Program

;Data Declarations-------------
    Param .BLKW 1   ; Holds value to pass in to the sub
    Result .BLKW 1  ; Holds result of sub
.END

This subroutine uses memory to pass in parameters and return results

The subroutine is a little more complicated because it needs to LD the value from Param and ST the result to Result memory locations

The main program is a bit more complicated because as well. But, this is because it must copy the previous Result into Param each time so that it can keep doubling the previous value. If this subroutine was normally going to double the same value, it should be redesigned so that the main code was simpler

asm
.ORIG x3000

    AND R2, R2, #0  ; Clear R2
    ADD R2, R2, #7  ; Place 7 in R2
    ST R2, Param    ; Copy value into memory for Sub to use
    
    AND R3, R3, #0  ; Clear R3
    ADD R3, R3, #3  ; Number of times to double
    
    JSR DoubleValue
    ;Result now contains 56
    
    AND R3, R3, #0  ; Clear R3
    ADD R3, R3, #5  ; Number of times to double
    
    JSR DoubleValue
    ;Result now contains 224
   
Done HALT

; Doubles the value in Param multiple times
;   inputs: Param contains the value to double
;           R3 contains the number of times to double the Param value
;   output: Result contains the doubled value
DoubleValue
    LD R4, Param
    
    Loop
        ADD R4, R4, R4  ; Double one time
        ADD R3, R3, #-1 ; Decrement R3
        BRp Loop        ; Do another double if R2 is not zero or negative
    
    ST R4, Result
    RET

;End of Program

;Data Declarations-------------
    Param .BLKW 1   ; Holds value to pass in to the sub
    Result .BLKW 1  ; Holds result of sub
.END

This subroutine uses a register and memory to pass in parameters and return results

Now the subroutine is more complicated because it doubles the value in Param based on the value in R3

The main program is simpler, only needing to load the value into Param and setting R3 to the number of times to double

RET

RET is similar to return in Java. However it is different it 2 ways:

  1. It must be the last line of the subroutine. If it is omitted, the main program will not continue executing
  2. It does not allow a return value. If the subroutine returns a result, it must be placed in a register or memory before RET is executed
asm
.ORIG x3000

    AND R2, R2, #0; Clear R2
    ADD R2, R2, #7; Place 7 in R2
    
    JSR DoubleValue;
    ;R2 now contains 14
    
    JSR DoubleValue;
    ;R2 now contains 28
    
    JSR DoubleValue;
    ;R2 now contains 56
   
Done HALT

; Doubles the value in R2
;   inputs: R2 contains the value to double
;   output: R2 contains the doubled value
DoubleValue
    ADD R2, R2, R2
    RET

;End of Program

;Data Declarations-------------
.END

Safeguarding Registers

The eight (8) general purpose registers in the LC-3 are shared by the main program and all subroutines. This means if main sets a value in a register and a subroutine changes it, the value is changes forever. The main program will not know the value was changes, leading to possible errors

There are two (2) ways to prevent problems with registers when using subroutines:

  1. Document the registers that will be purposefully changed by the subroutine
  2. Save/Restore temp registers inside the subroutine

Document the Registers

This safeguard does not protect data. It makes clear what will be modified so there are no surprised

Include a block comment at the top of the subroutine declaring all registers that will be purposefully changed by the subroutine

Purposeful changes are those that are required by the subroutine to execute. Typically those registers used to pass values into the subroutine and return results back to the main program

asm
.ORIG x3000

    AND R2, R2, #0; Clear R2
    ADD R2, R2, #7; Place 7 in R2
    
    JSR DoubleValue;
    ;R2 now contains 14
    
    JSR DoubleValue;
    ;R2 now contains 28
    
    JSR DoubleValue;
    ;R2 now contains 56
   
Done HALT

; Doubles the value in R2
;   inputs: R2 contains the value to double
;   output: R2 contains the doubled value
DoubleValue
    ADD R2, R2, R2
    RET

;End of Program

;Data Declarations-------------
.END

It is up to the main program to make sure no important values in the registers identified in the subroutine's block comments

Save/Restore Temp Registers

This safeguard does actually protect data by copying value to memory before changing a register. Then restoring the values just before the RET instruction

It is best that the subroutine save all registers it uses and are not listed in the subroutine block comment. These are registers the subroutine need to use to complete its function but are not part of the params or return registers

There is not guarantee that the main program is using the subroutine's temp registers. But, to be safe, the subroutine must always save/restore temp registers to ensure it does not cause an unexpected side-effect in the main program

Main program behaving weird when using subroutines?

Check that the registers used in the subroutine are either Documented or are being Saved and Restored

asm
.ORIG x3000

    AND R2, R2, #0  ; Clear R2
    ADD R2, R2, #7  ; Place 7 in R2
    ST R2, Param    ; Copy value into memoery for Sub to use
    
    AND R3, R3, #0  ; Clear R3
    ADD R3, R3, #3  ; Number of times to double
    
    JSR DoubleValue
    ;Result now contains 56
    
   
Done HALT

; Doubles the value in Param multiple times
;   inputs: Param contains the value to double
;           R3 contains the number of times to double the Param value
;   output: Result contains the doubled value
DoubleValue
    ST R3, SaveR3   ;Backup the value in R3
    ST R4, SaveR4   ;Backup the value in R4
    
    LD R4, Param
    
    Loop
        ADD R4, R4, R4  ; Double one time
        ADD R3, R3, #-1 ; Decrement R3
        BRp Loop        ; Do another double if R2 is not zero or negative
    
    ST R4, Result
    
    LD R3, SaveR3   ; Restore the original R3 value back into R3
    LD R4, SaveR4   ; Restore the original R3 value back into R3
    RET
    
    ;Memory locations for saving values on registers using in this sub
    SaveR3 .BLKW 1  ;Storage for R3
    SaveR4 .BLKW 1  ;Storage for R4

;End of Program

;Data Declarations-------------
    Param .BLKW 1   ; Holds value to pass in to the sub
    Result .BLKW 1  ; Holds result of sub
.END

Lines 38 & 39 allocate 2 memory locations to store values of 2 registers

Lines 21 & 22 save the register values into those memory location

Lines 33 & 34 Restore the value from memory into the original registers

The main program will have the same values in R3 and R4 and not be aware they were used by the subroutine

The contents of this E-Text were developed under an Open Textbooks Pilot grant from the Fund for the Improvement of Postsecondary Education (FIPSE), U.S. Department of Education. However, those contents do not necessarily represent the policy of the Department of Education, and you should not assume endorsement by the Federal Government.
Released under Creative Commons BY NC 4.0 International License