CMPUT 229 - Computer Organization and Architecture I

If/Elif/Else Conditional

229 RISC-V Examples - If/Elif/Else Conditional

Examples of Code Patterns and Structure in RISC-V - Basic Subroutines

Basic Functions

This page introduces functions and their basic structure.

What is a function?

Functions, also called procedures or subroutines, begin with a label. The last instruction executed by a function is a return statement. Typically this return statement is the last instruction that appears in the code of the function, but that is not a requirement as it may also appear in the middle of the function code. A return statement transfers the control of execution back to the caller and uses the return address register ra to specify the address where execution should return to. In RISC-V there are multiple instructions that can be used for this return statement: jalr/jal/j and ret.

Important: The execution of a function cannot end with a branch or jump to a label. Also, a function should not end by terminating the program. It must return to its caller.

A function is a "leaf function" when it does not call any other functions. The execution of a program through varios functions can be represented by a call graph -- in general it is not a tree because it contain cycles. Any node in this graph that does not contain an outgoing edge is a leaf of the graph.

A function starts with a label:

foo:
    ...

This is just a regular label, but in the syntax of our program it represents a function. What makes it a function is the fact that this label is used as the target of a function call instrution jal. The code of the function itself is likely to contain more labels. These labels are not interpreted as the start of a function because they are never the target of a jal instruction.

foo:

    # Content of foo

_fooLabel1:
    # Internal label of foo

    jalr zero, ra, 0

Consider prefixing label names differently so its easier to tell them apart from function labels. These non-function labels can also be indented but it is common to leave them un-indented.

Calling a Function

The instruction jal is the jump-and-link instruction. It is used to call a function. Here is an example where a function foo calls another function called bar after setting the parameters for bar to be a0=0 and a1=1:

foo1:
    addi    a0, zero, 0     # a0 <- 0
    addi    a1, zero, 1     # a1 <- 1
    jal     ra bar          # Call bar(0, 1)
    add     s0, zero, a0    # s0 <- a0

In this example, the return address for this call for the function bar is the address of the add s0, zero, a0 instruction because this is the instruction that must be executed immediately after the execution of bar. The word zero represents the register zero that always contains the value zero. An addi instruction is an add immediate instruction that adds a constant to a register. For more information on passing in arguments as seen in this example with setting a0=0 and a1=1, see page 02b - Argument and Return Values.

Fall-through Control Flow

A label may appear in the assembly code even when it is not preceded by an instruction that transfer control flow -- such as a branch or a jump. Consider this example:

foo2:
    # Initialize t0.
    add   t0, zero, zero      # t0 <- 0
    bne   a0, zero, _skip     # if a0 == 0, skip initialization of t1
    add   t1, zero, zero      # t1 <- 0
_skip:
    sub t2, a1, t0

In the above example if there there is no branch or jump instruction between the add t1, zero, zero instrucion and the label _skip, therefore the execution "falls through" to the sub instruction.

Returning From a Function

The instruction jalr zero, ra, 0 returns execution to the instruction immediately after the function call.

There are alternative instructions that can be used in its place. In layman's terms, the instruction is telling the processor to "go back to where this function was called". That is, return back to the function that called this one.

It is often a good idea to give the above instruction its own label and limit the exit points in the routine to just a few, if not just one. If the function is short it may not be necessary though. This "exit label" also gives the programmer a place to do any last-minute tasks before leaving. Below is a code snippet of a function with an exit label:

bar:
    # bar code...
    addi t0, zero, 31       # t0 <- 31

    _barLabel:
        # Also showing the indenting of a label inside of a routine.
        # This is ok style but not the usual convention.
        addi t0, zero, 30   # t0 <- 30

        # Say we wanted to exit here. Instead of having another exit point, we
        # can just jump to _barDone.
        j _barDone

    # Another super useful command.
    sub t0, zero, t0        # t0 <- 0 - t0

    # And if we didn't jump to _barDone earlier this falls through and goes to it
    # anyways, which is what we want.

_barDone:
    # At this point we are done and want to return.
    jalr  zero, ra, 0

Link to a file containing all of the above code snippets