What Is LLVM?

What Is LLVM?

When you look into how Rust is compiled, the name LLVM comes up. Here, I will compare it with Clang and GCC, familiar from C and C++, to clarify LLVM's role and see what rustc delegates to LLVM. LLVM is infrastructure for building compilers LLVM is not the name of a programming language. It is a collection of reusable components and technologies for building compilers and related tools. See the official LLVM overview. The name LLVM can refer to the entire project or to its optimization and code generation infrastructure. When we say that Rust compilation "passes code to LLVM," we mainly mean the latter. That includes tasks such as transforming a calculation into a more efficient form and selecting instructions for ARM64 or x86-64. Language implementations can share this infrastructure instead of building those parts from scratch. LLVM IR, on the other hand, is a language used during compilation. IR stands for Intermediate Representation. It helps to distinguish LLVM, the infrastructure, from LLVM IR, the representation of code that it processes. Name What it refers to LLVM Compiler infrastructure and tools; depending on context, its optimization and code generation infrastructure in particular LLVM IR The intermediate representation LLVM uses for optimization and code generation rustc The Rust compiler, which normally uses LLVM for code generation Clang A compiler for C, C++, and related languages that uses LLVM GCC A collection of compilers for multiple languages, with its own optimization and code generation infrastructure We will return to Clang and GCC later. First, I want to separate the infrastructure from the representation of code used inside it. Compilation is more than replacing one instruction with another "Turning source code into machine code" might suggest replacing each line of Rust with CPU instructions in sequence. In practice, several stages sit between the two. The compiler parses the source, resolves what names refer to, and checks that types fit together. For Rust, it also needs to check ownership and borrowing rules. It then transforms the program through different representations, removes unnecessary computations, and selects instructions suitable for the target CPU. Eventually, it determines registers and instruction sequences and generates machine code. The part that handles a language's syntax and meaning is often called the frontend; the part that analyzes and optimizes intermediate representations, the middle end; and the part that generates target-specific code, the backend. Actual implementations do not always have neat boundaries between those three. See the rustc overview and LLVM code generator documentation. LLVM provides shared components for tasks including optimization and code generation. Using LLVM does not automatically give a compiler an understanding of Rust's syntax or ownership rules. What does rustc delegate to LLVM? When using the usual LLVM backend, rustc analyzes and checks Rust source, processes intermediate representations such as MIR, and generates LLVM IR. MIR is an intermediate representation on the Rust side. It is used for borrow checking and Rust-side optimization too. The division of work is not "leave every optimization to LLVM." See Rust's MIR. LLVM then optimizes the LLVM IR and generates code for the target CPU. The resulting object files are passed to a linker. See rustc code generation. Rust source code ↓ rustc parses, type-checks, and more Rust-side intermediate representations (including MIR) ↓ Borrow checking, optimization, preparation for code generation, and more LLVM IR ↓ LLVM optimizes and generates code for the target CPU Object files ↓ Linker Executables and other linked outputs Enter fullscreen mode Exit fullscreen mode This is a simplified view of the division of work. Intermediate representations and stages are omitted. "Passing code to LLVM" does not normally mean that rustc writes a text file ending in .ll and asks another command to read it. rustc's LLVM backend uses LLVM's API to construct and process LLVM IR in memory. A .ll file is a human-readable rendering of that IR. See LLVM IR representations and rustc's LLVM IR generation. The compiler we use for Rust is rustc. LLVM is shared infrastructure incorporated into part of its work. What does addition look like after it reaches LLVM? Let's compile a small function to make this more concrete. The outputs below were produced with rustc 1.96.0 on an Apple Silicon Mac. Save this as add.rs: pub fn add(a: u32, b: u32) -> u32 { a.wrapping_add(b) } Enter fullscreen mode Exit fullscreen mode The function adds two u32 values. wrapping_add wraps the result around if it does not fit in a u32. I use it here because I want to observe the addition itself without including panic handling for overflow. See u32::wrapping_add. The following command outputs LLVM IR, assembly, and an object file: rustc add.rs --crate-name llvm_intro --crate-type lib --edition 2024 \ -C opt-level=2 -C link-dead-code --emit=llvm-ir,asm,obj Enter fullscreen mode Exit fullscreen mode --crate-type lib treats the source as a library without a main. -C opt-level=2 enables optimization so we can inspect the shortened computation. Since this example has no caller, I also use -C link-dead-code to keep the function's code from being removed. We stop before linking and request the three output formats explicitly. See rustc output options and code generation options. Opening llvm_intro.ll, I found this definition of add. The file header and attribute definitions outside the function are omitted. define hidden noundef i32 @_ZN10llvm_intro3add17he381aecb720e834bE(i32 noundef %a, i32 noundef %b) unnamed_addr #0 { start: %_0 = add i32 %b, %a ret i32 %_0 } Enter fullscreen mode Exit fullscreen mode Set the long function name and attributes aside for a moment. Start with these two lines: %_0 = add i32 %b, %a ret i32 %_0 Enter fullscreen mode Exit fullscreen mode i32 is a 32-bit integer type, and %a and %b are the argument values. add adds them, names the result %_0, and ret returns it. The Rust u32 type and method-call syntax are not preserved as they appeared in the source. The operation is now expressed as "add two 32-bit values and return the result." LLVM IR's i32 type itself does not distinguish signed from unsigned integers; operations and other details express those distinctions where needed. This wrapping addition is represented by add i32 without additional flags. See LLVM IR integer types and the add instruction. An LLVM IR add is not a CPU add instruction Next, open the generated llvm_intro.s. The function's instructions are: add w0, w1, w0 ret Enter fullscreen mode Exit fullscreen mode This is ARM64 assembly. w0 and w1 are 32-bit CPU registers, and add adds the two values and puts the result in w0. Here, the computation uses the registers holding the arguments, and the return value is placed in w0 too. Wait, LLVM IR also had an add. They are not the same thing, though. LLVM IR values such as %a and %_0 are not CPU register names. LLVM reads the relationships between these values and lowers them into instructions and registers for the target CPU. In this case, that produced ARM64's add w0, w1, w0. We can inspect the machine code in llvm_intro.o too. This command uses llvm-objdump from Xcode Command Line Tools: xcrun llvm-objdump --disassemble --demangle llvm_intro.o Enter fullscreen mode Exit fullscreen mode Here are the instruction lines from its output: 0: 0b000020 add w0, w1, w0 4: d65f03c0 ret Enter fullscreen mode Exit fullscreen mode From left to right, each line shows the instruction's location, its machine-code encoding, and a readable representation of the instruction. Our LLVM IR addition has become an instruction the CPU can execute. LLVM IR was not the machine code itself. It represented the computation at an earlier stage. LLVM IR is sometimes described as CPU-independent, but that does not make it universally portable code you can move unchanged to any environment. This .ll file includes a target triple identifying the target and a target datalayout describing data layout. Target-specific details such as calling conventions and pointer sizes also matter. See LLVM IR data layout. Why doesn't rustc implement everything itself? It is possible to implement machine-code generation specifically for Rust. But handling the instructions and register constraints of each supported CPU, while continuously improving optimization, requires substantial implementation and maintenance work. Using LLVM lets rustc translate Rust's operations into LLVM IR and share the optimization and CPU-specific code generation that follow. Clang uses LLVM too. Clang handles the meaning of C and C++, then uses infrastructure shared with Rust from LLVM IR onward. See Clang's official site. Figure 1: Language-specific processing and the parts shared through LLVM. The arrows show the use of common infrastructure, not a merger of separate programs' IR into one program. For example, when LLVM's optimizations improve, compilers for multiple languages can incorporate that improvement. Sharing CPU-specific code generation also reduces the burden of maintaining a separate implementation for each language. Of course, adding support for a CPU in LLVM does not mean Rust automatically supports everything about it. Rust-side target configuration, standard library support, and other work are still needed. The benefit is the large amount of infrastructure that can be shared. See why rustc uses LLVM. How do LLVM and GCC differ? Both LLVM and GCC have mechanisms for sharing optimization and CPU-specific code generation across multiple languages. They provide separate implementations for the task of turning source code into machine code. LLVM provides optimization and code generation functionality as libraries. Clang and rustc translate their respective languages into LLVM IR and use that infrastructure. GCC stands for GNU Compiler Collection. It includes language-specific processing, intermediate representations, optimization, and CPU-specific code generation. GCC also shares processing across multiple languages, but its implementation is separate from LLVM's. GIMPLE, for example, is one of the intermediate representations it uses for optimization. See GCC's official site and GCC's GIMPLE documentation. What we are comparing LLVM GCC Language-specific processing Clang, rustc, and other compilers handle their respective languages GCC's language-specific frontends handle it Intermediate representations used for optimization LLVM IR GIMPLE and others CPU-specific code generation Uses LLVM's backends Uses GCC's backends The distinction is not that only LLVM offers reusable components while GCC can only be used as a monolithic command. GCC also has libgccjit, a library that lets external programs use code generation and related functionality. See the libgccjit documentation. When writing C or C++, choosing between Clang and GCC means choosing a compiler as a whole, from language processing through machine-code generation. Choosing Clang means using LLVM's infrastructure for optimization and code generation. See Clang. For Rust, rustc is the compiler handling the language. It does not ask Clang to read Rust. rustc and Clang each handle their own languages while using LLVM's infrastructure. Can Rust use GCC? Can Rust code generation use GCC too? The usual rustc configuration uses LLVM, but there are efforts to use GCC. It is useful to distinguish replacing rustc's code generation backend from implementing a Rust frontend within GCC. See rustc code generation backends. The first approach is rustc_codegen_gcc. It keeps rustc's parsing, type checking, borrow checking, and other Rust-side functionality, while using GCC instead of LLVM for code generation. This backend uses libgccjit, mentioned above. Despite JIT in its name, the project uses it for ahead-of-time compilation: generating machine code before execution. See rustc_codegen_gcc. The second is gccrs. Instead of replacing rustc's backend, this project implements a Rust frontend within GCC and connects it to GCC's optimization and code generation. See gccrs. Configuration The side that parses and checks Rust code Optimization and code generation infrastructure Usual rustc rustc LLVM rustc with rustc_codegen_gcc rustc GCC through libgccjit gccrs Rust frontend within GCC GCC This table shows the division of work. As discussed earlier, rustc also performs optimizations on its side. One goal of rustc_codegen_gcc is to make Rust available on platforms that LLVM does not support. Using GCC opens another path for target support and optimization. Which one is faster, however, needs to be evaluated for a particular program and target environment. As of October 2026, these efforts remain under development. That does not mean they have the same language-feature coverage or setup requirements as the usual rustc configuration. For example, the rustc_codegen_gcc README says it requires a patched libgccjit. Here, the focus is on their different structures rather than installation instructions. See rustc_codegen_gcc requirements and gccrs development. Rather than "Rust doesn't use GCC," the situation is that Rust normally uses LLVM, while alternative configurations using GCC are also being developed. Rust the language and LLVM the machine-code generation infrastructure are separate things. LLVM's role in Rust compilation The LLVM project as a whole also includes tools beyond Clang, such as the linker LLD and the debugger LLDB. That is why looking up LLVM brings up tools beyond machine-code generation. See LLVM subprojects. To understand Rust compilation, it helps to distinguish rustc, which handles Rust's rules; LLVM IR, which represents the intermediate computation; and LLVM, which performs further optimization and code generation. We may type just one command, rustc, but inside it, Rust-specific mechanisms work together with infrastructure shared with other languages.

Original Source

Read the full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.