Skip to content

Introduction to LLVM

I am going to attempt to introduce LLVM in an easy way, and it’s going to be a test for myself too.

Compilers translate source code in your language to a format understood by the machine the code is supposed to run on. This machine is usually a processor (CPU is one of them), and there are multiple vendors that build these - Intel, AMD, Apple Silicon, smaller embedded devices (ESP32), etc. For your language to run on a set of processors, your compiler would have to target the instruction set architectures (ISA) for all of these processors, meaning you would have to write software for all bespoke targets you would want to cover.

This was a huge problem in the industry as device vendor space grew rapidly and software builders had to catch up to keep their software working on platforms their users were on. LLVM was developed by Chris Lattner as his masters project to solve this problem. LLVM’s proposal was:

What if there’s an intermediate representation (IR) that is more absract and covers most of the functionality of ISAs? Compiler frontends could then just target this IR and LLVM would reuse tooling to build backends to target multiple platforms.

This is a classic software problem where an abstraction is introduced to reduce the amount of work needed. LLVM offers the following:

  • an intermediate representation syntax
  • tooling to produce this IR from your compiler frontends
  • tooling to perform optimisation passes over the generated IR
  • backends to generate target ISA-specific machine code
  • in addition to this, tools like clang, lldb, llc

As compiler developers, we should be aware of what the LLVM IR allows us to do. Before that, let’s quickly build a mental model: LLVM IR is register-based language, i.e. it uses registers to perform operations in the program. It assumes an infinite number of registers and uses the static-single assignment form - we’ll look at what these mean soon.

%2 = add i32 %0, 1 ; adds 1 to register %0 and sets to register %2

Registers are denoted by a % followed by a name (either a number or text). Operations are written in the <operation> <type> <inputs> form, and there are bunch of them. A few important concepts we should know:

In LLVM IR, each program has a module, a set of functions, a set of basic blocks and a set of instructions. Our Gom compiler will generate this code for a given source code.

  • Module: A package/module, similar to other languages which holds a set of functions and other entities and may export them to use in other modules.
  • Function: A regular function which groups a set of instructions and logic. Has a name, a return type and a set of typed input arguments.

// this page is a work-in-progress