RISC-V Assembly Language - Jump Table

Introduction

A jump table in RISC-V assembly is an efficient way to implement multi-way branching (like a switch statement in C). Instead of executing a long chain of conditional branches, you store a list of target memory addresses in an array and use an index to load and jump directly to the correct destination.

In RISC-V, this is achieved using the jalr (Jump and Link Register) instruction, which allows jumping to an address calculated inside a register.

Code

This an implementation of a jump table for a basic calculator module (0 for Add, 1 for Subtract, 2 for Multiply).

# jump table demonstration code .data # ---- jump table containing target memory addresses .align 2 jump_table: .word do_add .word do_sub .word do_mul .text .globl main main: # ---- example inputs li t0, 1 # t0 = index (e.g., 1 for subtract) li a0, 20 # a0 = operand A li a1, 5 # a1 = operand B # ---- bounds check (crucial! ensures index is between 0 and 2) li t1, 3 bltu t0, t1, valid # If index < 3, proceed j default_case # Else, go to fallback valid: # ---- calculate address offset (index * 4 bytes for 32-bit words) slli t0, t0, 2 # t0 = index * 4 # ---- load the base address of the jump table la t2, jump_table # t2 = base address of jump_table # ---- get the target address from the table add t2, t2, t0 # t2 = jump_table + (index * 4) lw t3, 0(t2) # t3 = target address (e.g., address of do_sub) # ---- jump to the target address jr t3 # Unconditional jump to register address # (pseudo-op for jalr x0, 0(t3)) # --- branch targets do_add: add a2, a0, a1 # a2 = 20 + 5 = 25 j end do_sub: sub a2, a0, a1 # a2 = 20 - 5 = 15 j end do_mul: mul a2, a0, a1 # a2 = 20 * 5 = 100 j end default_case: li a2, -1 # Return -1 for invalid index end: # ---- result is now in a2

Code found on the web. Untested.

Breakdown of Steps

  1. Table Structure (.word): The jump table sits in the data segment as an array of pointers to labels.

  2. Bounds Checking (bltu): Using Branch Less Than Unsigned (bltu) serves a double purpose. It catches indexes that are too high, and automatically catches negative inputs (since a negative integer evaluates to a massive unsigned integer).

  3. Index Shifting (slli): Because addresses are word-aligned, each index corresponds to a 4-byte offset in a 32-bit architecture (RV32). Shift the index left by 2 bits (slli index, index, 2) to multiply by 4.
    Note: For a 64-bit architecture (RV64), use .dword pointers, and shift left by 3 bits (slli index, index, 3) to multiply by 8.
  4. Indirect Jump (jr / jalr): The jr t3 instruction changes the program counter (PC) directly to the value residing in t3. Because it passes x0 to the hardware layer underneath, it performs the jump without saving a return address.