This is a write-up about riscv rv64 and the assembly generated by GCC.

The example Link to heading

I am using a pre-built rv64 toolchain from the package gcc-riscv64-linux-gnu

apt install gcc-riscv64-linux-gnu
riscv64-linux-gnu-gcc -v

The example i am using is very simple. Basically setting a variable on the stack (will see later why we need this) then return that variable.

int main(){
    int x;
    x = 0;
    return x;
}

riscv64-linux-gnu-gcc is used with a twist. This toolchain is using the compressed instructions. To see the full 32 bits, i had to pass the following options to gcc

riscv64-linux-gnu-gcc main.c -march=rv64imafd -mabi=lp64d

vanilla riscv64-linux-gnu-objdump is used to see the generated assembly

riscv64-linux-gnu-objdump -d a.out

Jumping to the disassembled main:

00000000000005ec <main>:
 5ec:   fe010113                addi    sp,sp,-32
 5f0:   00813c23                sd      s0,24(sp)
 5f4:   02010413                addi    s0,sp,32
 5f8:   fe042623                sw      zero,-20(s0)
 5fc:   fec42783                lw      a5,-20(s0)
 600:   00078513                mv      a0,a5
 604:   01813403                ld      s0,24(sp)
 608:   02010113                addi    sp,sp,32
 60c:   00008067                ret

Note that the register names are not x0-x31 as mentioned in the specs because gcc is using ABI calling convention defined in Chapter 25 of RISCV spec Example image

addi is using the following opcode where sp is mapped x2. So, it’s easy to understand what fe010113 means here. Example image

Assembly walk-through Link to heading

while we are here, we can see the walk-through the generated assembly annotated with explanation for each line.

First, gcc generates boiler-plate code for stack and frame pointers. This is done for all architectures to setup the function frames for successive function calls.

00000000000005ec <main>:
 5ec:   fe010113                addi    sp,sp,-32 # move stack pointer up to init stack for the function
 5f0:   00813c23                sd      s0,24(sp) # store s0 (frame pointer callee saved) on the stack
 5f4:   02010413                addi    s0,sp,32  # move frame pointr for the current function
...
...
...
 604:   01813403                ld      s0,24(sp) # restore fp
 608:   02010113                addi    sp,sp,32  # restore sp
 60c:   00008067                ret

And the actual code:

00000000000005ec <main>:
 5f8:   fe042623                sw      zero,-20(s0) # store 0 to variable x on the stack. Note it's using fp to reference it.
 5fc:   fec42783                lw      a5,-20(s0)  # main return x, so it loads the variable to a5 
 600:   00078513                mv      a0,a5       # move a5 to a0 (return value according to calling convention)