CMPUT 229 - Computer Organization and Architecture I

For Loops

229 RISC-V Examples - For Loops

This page introduces the basic structure needed to implement a for-loop in RISC-V. Consider the following for loop in a higher level language:

for (int i = 0; i < n; i = i + 1) {
    total = total + i * i;
}

In particular, there are four components of the loop's control flow we want to address: initialization, the loop condition, update statement, and the body of the loop. The initialization code should only be run once before the loop begins, and in our case we are initializing i = 0. The loop condition runs before every iteration checking if it should continue looping or not, and the update step runs at the end of every iteration which updates something, and in our case increasing i by 1. Finally, between the loop condition and the update step, we have the body of the loop. The complete control flow for our loop is shown in the figure below.

Similar to while-loops (04b), we will have a label and guard branch at the start of the loop, and a branch at the end of the body of the loop, checking if we should continue. In addition to this we will have some initialization instructions before the loop and an instruction incrementing i.

# Register usage:
# t0: i
# t1: n
# t2: total
# t3: Intermediary for calculations

    # Start of for loop.
    addi    t0, zero, 0     # i = 0
    bge     t0, t1, _joinPoint # If i >= n, our condition is false
_forLoop:
    mul     t3, t0, t0      # t3 <- i * i
    add     t2, t2, t3      # total = total + i * i

    # Now we increment i and loop.
    addi    t0, t0, 1       # i = i + 1
    blt     t0, t1, _forLoop # Checking if i < n.
_joinPoint:
    # Code after loop.
    # ...

While the above code does indeed work, it does not give us much room to modify it as we see fit. We will add in a new label _forLoopNext which signifies that the body of the loop is done, and we should both perform the update step and check to see if we should continue looping.

_forLoop:
    # Body of for loop.
    # ...

_forLoopNext:
    # Increment i and check condition.
    addi    t0, t0, 1
    blt     t0, t1, _forLoop

This achieves two things: first, it gives an explicit place to perform the necessary steps for the loop making it easier to read and debug, and second it allows us to include (the equivalent of) a continue statement in our loop. If we want to execute the continue statement at any point, simply jump to the _forLoopNext label. (e.g. jal zero, _forLoopNext).

In addition, we can also jump to _joinPoint as a break statement.

    # Start of for loop.
    addi    t0, zero, 0     # i = 0
    bge     t0, t1, _joinPoint # If i >= n, our condition is false

_forLoop:
    mul     t3, t0, t0      # t3 <- i * i
    add     t2, t2, t3      # total = total + i * i

_forLoopNext:
    # Now we increment i and loop.
    addi    t0, t0, 1       # i = i + 1
    blt     t0, t1, _forLoop # Checking if i < n.

_joinPoint:
    # Code after loop.
    # ...

A link to a full implementation of the above is below.

Variants

It is worth emphasizing that this is just an example of a for-loop in RISC-V using a guard branch. For a for loop without one, we can make a similar modification as with while-loops. On top of that, for simpler loops, we can exclude labels entirely if they are redundant.

Link to an implementation of a for loop