Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 7

Decoding LLVM IR: Functions, Blocks, and Instructions

Recommended resources

Welcome back. In our previous lesson, we established the conceptual foundation of LLVM IR: Static Single Assignment (SSA). You learned why the IR is structured to ensure every value has a single definition, and how this property is the key that unlocks a vast range of powerful optimizations.

Now, we will move from the "why" to the "what." This lesson dives into the concrete syntax of LLVM IR. Your goal is to become comfortable reading and interpreting the fundamental components you'll encounter in any .ll file: functions, basic blocks, and instructions. Mastering this syntax is the first practical step toward analyzing compiler output, understanding bug reports, and eventually, writing your own code transformations.

We will begin with the high-level hierarchy of an IR file and progressively zoom in on the details of each component, building on your understanding of SSA as we go.

The Anatomy of an LLVM Module

LLVM IR is not a flat list of commands; it's a structured, hierarchical representation of a program. This organization is key to how LLVM manages and optimizes code. The structure is as follows:

  1. Module: The top-level container, representing a single translation unit (like a .c or .cpp file). It contains global variables, function declarations, and function definitions.
  2. Function: A callable unit of code within a module. It has a name, a signature (arguments and return type), and contains one or more basic blocks.
  3. Basic Block: A straight-line sequence of instructions with no branches in or out, except at the beginning and end. Every basic block ends with a single "terminator" instruction that transfers control flow.
  4. Instruction: A single operation, like an addition, memory load, or branch.

The following video from the EuroLLVM 2019 tutorial provides a quick visual overview of this structure.

2019 EuroLLVM Developers’ Meeting: V. Bridgers & F. Piovezan “LLVM IR Tutorial - Phis, GEPs ...”

This segment walks through the hierarchical components of LLVM IR, from the Module down to the Basic Block.

Watch the section from "IR's Internal Elements". Pay attention to how each component contains the next, forming a clear hierarchy.

To make this more concrete, let's look at a simple example. The image below shows the IR for a Fibonacci function and labels its core syntactic parts.

This annotated snippet of LLVM IR for a Fibonacci function illustrates the key syntactic elements: the function definition, SSA registers, instructions, and data types. We will be examining each of these in detail.

Notice the identifiers starting with @ and %. These are called sigils, and they are a fundamental part of IR syntax:

  • @ denotes a global identifier. This includes function names (like @fib) and global variables. These have a single definition within a module.
  • % denotes a local identifier. This includes function arguments and the results of instructions. These are often called virtual registers. In keeping with the SSA principle from our last lesson, each virtual register is assigned a value exactly once.

Functions: The Units of Code

The primary component you'll interact with is the function. A function is introduced with the define keyword.

A Gentle Introduction to LLVM IR - mcyoung

This article provides a clear, example-driven breakdown of IR syntax. We will start with the section that introduces the simplest function definition.

Read the subsection "A Trivial Function". Focus on the syntax of the define keyword, the function signature (return type, name, arguments), and the use of the @ sigil for the function name.

As the reading shows, a function definition has a clear structure:
define <return_type> @<function_name>(<argument_list>) { ... }

For example:
define i32 @square(i32 %x) { ... }

Here:

  • define begins the definition of a function. (A declare keyword, by contrast, introduces a function without providing its body, similar to a forward declaration in C++).
  • i32 is the return type (a 32-bit integer). LLVM IR is strongly typed.
  • @square is the function's name.
  • (i32 %x) is the argument list. Here, it takes one 32-bit integer, which will be accessible within the function via the local identifier %x.

Basic Blocks and Instructions: The Flow of Logic

Inside a function are basic blocks. The first block in a function is special and is called the entry block. Other blocks are given labels, which act as targets for control flow instructions.

A basic block is a sequence of non-terminator instructions followed by exactly one terminator instruction. Terminator instructions are what create the control flow graph of the function. The most common terminators are:

  • ret: Returns control to the calling function.
  • br: A branch, which can be unconditional (jumping to a single block) or conditional (jumping to one of two blocks based on a boolean value).
  • switch: A multi-way branch, similar to a C switch statement.

Let's examine the instructions themselves.

This image categorizes the most common LLVM IR instructions. We will focus on binary, terminator, and call operations in this lesson.

The general format for an instruction that produces a value is:
%result = <opcode> <type> <operand1>, <operand2>, ...

Let's read about a few of the most important instruction types.

A Gentle Introduction to LLVM IR - mcyoung

We'll now read three sections from the same article to understand the most common instructions.

First, read the section "Purely Scalar Code". This introduces basic binary arithmetic operations like add and mul. Note the explicit typing. Next, read the section "'I'll Be Back'", up to the discussion of the switch instruction. This covers the icmp (integer comparison) instruction and the br (branch) terminator, which together form the basis of all conditional logic. Finally, read the short section on "Function Calls". This explains the call instruction, which is how functions invoke each other.

Let's put this together with an example. Consider this C++ function:

int my_func(int a, int b) {
  if (a > 10) {
    return a + b;
  }
  return b;
}

The corresponding LLVM IR might look like this (simplified):

define i32 @my_func(i32 %a, i32 %b) {
entry:
  %cmp_result = icmp sgt i32 %a, 10   ; Compare %a > 10. Result is an i1 (boolean)
  br i1 %cmp_result, label %if_true, label %if_false ; Conditional branch

if_true:
  %sum = add i32 %a, %b              ; Perform addition
  ret i32 %sum                         ; Return the sum

if_false:
  ret i32 %b                         ; Return %b
}

By reading this IR, you can trace the logic:

  1. The entry block compares %a to the constant 10.
  2. The br instruction uses the i1 result of the comparison to transfer control to either the %if_true block or the %if_false block.
  3. The %if_true block computes the sum and returns it.
  4. The %if_false block returns %b directly.

This is the essence of reading IR: following the definitions of %-registers and tracking control flow through terminator instructions. Your understanding of SSA is critical here, as you know that %a and %b have a fixed value throughout the function, and new values like %cmp_result and %sum are created for the results of each operation.


Conclusion

In this lesson, we have taken our first major step into the practical world of LLVM by learning to read its language. You can now dissect a piece of IR and identify its primary structural and syntactic elements.

Key Takeaways:

  • Hierarchy: LLVM IR is organized hierarchically into Modules, Functions, Basic Blocks, and Instructions.
  • Identifiers: Global identifiers (functions, globals) start with @. Local identifiers (virtual registers, arguments) start with %.
  • Functions: Defined with the define keyword, they have a type signature and contain a set of basic blocks.
  • Basic Blocks: These are sequences of instructions that start with a label and end with a single terminator instruction (ret, br, switch, etc.), which dictates control flow.
  • Instructions: Operations like add, icmp, and call follow a consistent format: %result = opcode type operands.... They operate on constants or other virtual registers and produce a new virtual register, upholding the SSA property.

We have only scratched the surface of the LLVM type system by using i32 and void. In our next lesson, we will explore the type system in greater depth, covering the full range of primitive types, pointer types, and aggregate types like structs and arrays. This will equip you to understand more complex data structures and the instructions that operate on them.