This page covers how to pass arguments to a function and return values from
one. RISC-V has eight argument registers: a0
through a7
. Arguments are
passed to a function through these registers. Return values produced by a
function are passed back in registers a0
and a1
.
Consider a function foo
that calls a subroutine foil
which when given
number a
in register a0
and number b
in register a1
calculates
a^2 + 2*a*b + b^2
and prints the resulting value to stdout.
(Foils out (a + b)^2
) Here is one possible implementation of foo
and foil
:
#-------------------------------------------------------------------------------
# foo
# A function that does something, and as a sub-task foils two numbers.
#
# Arguments:
# None
# Return Values:
# None
# Side Effects:
# None
# Register Usage:
# t0, t1: Intermediaries to be foiled out.
#
#-------------------------------------------------------------------------------
foo:
# Imagine we are doing some task which results in t0, t1 having
# the values a, b which we want to foil out.
# Move a, b to a0, a1.
add a0, zero, t0 # a0 <- a
add a1, zero, t1 # a1 <- b
jal ra, foil # foil(a, b)
# Lastly, return from foo.
jalr zero, ra, 0
#-------------------------------------------------------------------------------
# foil
# Given two numbers a and b, this function foils them out.
# That is, expands (a + b)^2 into a^2 + 2ab + b^2.
#
# Arguments:
# a0: The value of a.
# a1: The value of b.
# Return Values:
# None
# Side Effects:
# Prints the sum of a^2 + 2ab + b^2 to terminal.
# Register Usage:
# t0, t1: Intermediaries in calculations.
#
#-------------------------------------------------------------------------------
foil:
# Calculate the squares.
mul t0, a0, a0 # t0 <- a^2
mul t1, a1, a1 # t1 <- b^2
add t0, zero, t1 # t0 <- a^2 + b^2
# Now the 2 * a * b term.
mul t1, a0, a1 # t1 <- a * b
add t0, t0, t1 # t0 <- a^2 + b^2 + a*b
add t0, t0, t1 # t0 <- a^2 + b^2 + 2*a*b
# To print it out, we need to move the value into a0 and the number 1 into a7.
mv a0, t0
addi a7, zero, 1
ecall
# Now we return to foo.
jalr zero, ra, 0
The order of the arguments, or what the arguments are needed, is up to the programmer implementing the function. If a function is created from a specification, the specification must precisely indicate which arguments the function expects, which values the function returns, and which side effects the function has. The function header should contain this information.
A function can return values in the argument registers a0
and a1
. Before returning,
a function must ensure that the expected return values are in the registers a0
or a1
.
Whenever possible, it is a good practice to already use these registers for the
computation of the values that will be returned. This practice eliminates the need for
move instructions prior to returning.
Consider the code example from before. In this variation the funtion foo
returns the
calculated value to instead of printing it:
foil:
# Calculating a^2 + 2*a*b + b^2 is the same.
# Move value to a0 and return.
mv a0, t0
jalr zero, ra, 0