Infraimplementation

What LLVM Actually Is

LLVM is compiler infrastructure: a collection of reusable libraries built around well-specified intermediate representations, with analyses, optimizations and code generators you link into your own program. Clang is one of its clients, not the thing itself.

The question

People keep saying "LLVM" as though it were one program — what is it actually?

SourceLexingTokensParsingASTSemanticsTypedIROptimizeCodegenMachine codeLinkExecute
What the program is here

A set of C++ libraries plus a specified data format. The libraries operate on an in-memory module: a container of global values, functions, and basic blocks of typed instructions in SSA form, with a verifier that can be asked at any point whether the structure is well-formed. Everything else — pass managers, analyses, target descriptions, an assembler, a disassembler, a linker, a JIT — is written against that one data structure. The question the design exists to answer is how a language implementer can obtain optimization and code generation for many targets without writing either.

What this phase may assume or do

A client may assume exactly what the IR specification and the verifier guarantee, and nothing more. It may assume that a module which passes llvm::verifyModule is structurally well-formed, that every value is defined before use along every path, and that a transformation which preserves the IR's defined semantics is legal. It may not assume that the C++ API is stable across releases, that any particular pass will run, or that undefined behavior in its own source language maps onto undefined behavior in the IR without being encoded explicitly — the flags and attributes that carry those assumptions are the frontend's responsibility to emit.

Key points

  • LLVM is compiler infrastructure — reusable libraries around specified intermediate representations — and the programs people run, like Clang, are clients of it.
  • Everything is written against one in-memory data structure: typed SSA instructions in basic blocks, with a verifier that can check it at any point.
  • The payoff is that a frontend author gets an optimizer and code generation for a dozen targets without writing either.
  • The uses go well beyond source-to-executable compilers: query JITs, shader compilers and accelerator toolchains use the same libraries.
  • The C++ API is unstable by policy; bitcode has a backward-compatibility window; textual IR has no compatibility promise at all.
  • Not using it is a legitimate choice with a visible price, and Go is the clearest mainstream example of making that choice deliberately.

Libraries, not a program

implementationThe component list is the LLVM monorepo as of roughly 2024–2025 and it grows: MLIR joined in 2019, Flang later, and several subprojects have been renamed or restructured. "LLVM" in a sentence written five years ago may mean a smaller thing than it does today, and in a paper from 2004 it means the original research system, whose name was an acronym that the project has since officially retired.

The single most common misunderstanding in this subject is treating LLVM as a compiler in the sense that gcc is one. It is not a program you run; it is a set of libraries you link against. What you run is a *client*: Clang, rustc, swiftc, the Julia runtime, zig, ldc, flang, or something you wrote. Each of those contains a frontend for one language and calls into LLVM for everything after the point where the program stops being about that language.

The distinction matters because it explains the shape of the entire ecosystem. Ten languages get a competitive optimizer and code generation for a dozen architectures without any of them writing an instruction scheduler, because the optimizer and the code generators are libraries rather than parts of somebody's compiler. That is the argument in [[multiple-frontends-one-backend]], made concrete.

It also explains the uses that are not compilers at all. Database engines JIT-compile query plans through LLVM. Graphics drivers compile shaders through it. Hardware vendors ship LLVM-based toolchains for their accelerators. None of those is "a compiler" in the sense of taking a source file to an executable, and all of them need the same thing: something that turns a typed instruction graph into good machine code.

What is inside the umbrella, and what each part is forimplementation
ComponentWhat it isWho uses it
LLVM CoreThe IR data structures, verifier, pass infrastructure, analyses and optimizationsEvery client, always
Code generationTarget descriptions, instruction selection, scheduling and register allocation per architectureAny client emitting machine code rather than IR
ClangA C, C++, Objective-C and CUDA frontend, plus a driver and a tooling libraryC-family users; and anything reusing its AST — see [[clang]]
LLDA linker for ELF, Mach-O, COFF and WebAssemblyBuilds that want a faster link than the system linker
MLIRA framework for defining *your own* IRs and lowering between themMachine-learning and domain-specific compilers
compiler-rt, libc++, libunwindRuntime support: builtins, sanitizers, a standard library, unwindingPrograms built by Clang; the sanitizers, by anything
LLDBA debugger built on Clang's expression parser and type systemAnyone debugging; notably Swift and C++ on Apple platforms

What the infrastructure actually buys you

Concretely: if you write a frontend that emits LLVM IR, you get an optimizer that has absorbed two decades of work, code generators for x86-64, AArch64, RISC-V, ARM, PowerPC, WebAssembly and more, an assembler and disassembler for each, a JIT you can call at run time, and the sanitizers. You write the part that is specific to your language and nothing else.

That trade is why the mainstream new languages of the last fifteen years overwhelmingly took it. Rust, Swift, Julia, Zig, Crystal and others all emit LLVM IR, and none of them has an instruction scheduler in its repository. The cost is real too — you inherit LLVM's compile times, its release cadence, and its model of what a program is — but the alternative is a multi-year project per architecture.

The counter-example is worth naming because it is instructive rather than contrarian. Go's toolchain deliberately does not use LLVM: it wanted compile speed, and it needed the backend to be tightly coupled to a runtime whose contract it changes. That is a defensible decision with a visible price on generated code quality, and it is the same trade seen from the other side — see [[go-pipeline]].

The stability contract, which is narrower than people expect

implementationBitcode backward compatibility is a project policy with a supported window, not a specification: LLVM aims to read bitcode from several prior major versions and has dropped support for very old formats. The textual IR has no such promise at all — it is a debugging and testing format, and IR written out by one release can fail to parse in the next. Never treat textual IR as an archival format.

Three different things get called "LLVM compatibility" and only one of them is a real promise. The C++ API is explicitly unstable: it changes every release, and out-of-tree passes and language frontends are expected to be updated for each. Anyone maintaining a compiler on top of LLVM budgets for this twice a year.

The IR is more stable but not fixed. New instructions, attributes and intrinsics are added, semantics are occasionally clarified, and textual IR from an old version may not parse in a new one. Bitcode carries a compatibility promise in one direction — newer LLVM reads bitcode produced by older versions, within a supported window — which is what makes [[link-time-optimization]] across a toolchain upgrade tolerable rather than impossible.

The licence is the part with actual history behind it. LLVM was under a BSD-style licence for most of its life and relicensed to Apache 2.0 with LLVM exceptions from version 9. The exceptions exist so that runtime library code compiled into your binary does not impose attribution obligations on the binary — a practical concern for exactly the embedded and proprietary users the permissive licence attracted. This matters here only because licence terms are one of the reasons an organisation picks one toolchain over another, and pretending otherwise would leave a real decision criterion out.

How it works

The steps, in the order the compiler takes them.

  • A frontend parses and checks its own language, then constructs an LLVM module: global values, functions, basic blocks and typed instructions in SSA form.
  • The frontend attaches the assumptions its language licenses — overflow flags, aliasing attributes, alignment, address spaces, debug locations — because LLVM cannot infer them from the source it never saw.
  • A pass manager runs analyses and transformations over the module, each pass declaring which analyses it needs and which it preserves so results are not recomputed unnecessarily.
  • The verifier can be run between passes to catch a malformed module at the pass that produced it rather than several passes later.
  • The code generator lowers the optimized IR to a target-specific machine IR, selects instructions, schedules them, allocates registers and emits either assembly or object code directly.
  • A client that wants run-time compilation uses the JIT APIs instead, compiling a module in-process and obtaining function pointers to the result.

How it breaks

What the engineer observes when it goes wrong — not what goes wrong internally.

  • An out-of-tree pass or frontend stops building after an LLVM upgrade, with compile errors in code that nobody changed, twice a year, forever.
  • A frontend emits IR that is technically well-formed but omits the attributes its language would have justified, and the resulting code is slower than the same program compiled from C, with nothing wrong that a diff would show.
  • Textual IR checked into a repository as a fixture stops parsing after an upgrade, and the test suite fails for a reason unrelated to the change under test.
  • A crash reproduces only at a particular optimization level and turns out to be a mismatch between what the frontend meant by an attribute and what the middle-end assumed it meant — a [[miscompilation]], and one of the hardest classes to attribute.
  • A team adopts LLVM expecting a compiler and discovers they still have to write a frontend, a driver, a runtime and a debugger story.

When it helps

  • Implementing a new language, where emitting IR is a fraction of the work of writing a backend and the result is competitive immediately.
  • Building anything that needs run-time code generation — a query engine, a regular-expression engine, a numeric kernel specialiser — without writing an assembler.
  • Targeting an unusual architecture, where a vendor LLVM backend may be the only mature toolchain that exists.
  • Reading compiler bug reports and release notes with the right mental model of which component is responsible for what.

When it hurts

  • Compile-time-sensitive builds. The optimizer and code generator are the dominant term in build time for languages that use them, and there is no configuration that makes them cheap.
  • Languages whose semantics do not fit the IR's model — precise garbage collection, exact exception semantics and unusual memory models all require work that a bespoke backend would not.
  • Small or embedded projects where the binary size and build dependency of the libraries outweigh what they provide.

What it costs

Every one of these is paid by something.

  • Reusing the infrastructure buys a mature optimizer and a dozen code generators for the price of a frontend, and pays with an unstable API that must be tracked every release and a compile-time cost you do not control.
  • Standardising on one IR buys the M+N economics that make the whole ecosystem work, and pays by making every language's semantics fit a model that was designed around C — precise GC, exceptions and language-specific memory models all take extra effort.
  • A permissive licence buys adoption by proprietary and embedded users, and pays by making it possible for improvements to stay private — which is a real difference from GCC's position and a reason organisations choose differently.
  • Shipping many subprojects under one umbrella buys a consistent, co-versioned toolchain, and pays with a build that is enormous and a name that means different things in different sentences.

What else you could do

What a different compiler or language does instead, and when that is better.

  • GCC is the other mature option: a complete toolchain with its own IRs and its own optimizers, historically less reusable as a library — see [[gcc]].
  • Cranelift is a code generator designed for compile speed rather than peak optimization, used by WebAssembly runtimes and by Rust's debug-build backend.
  • QBE and libFirm are much smaller backends that trade optimization quality for something a single person can understand and modify.
  • Writing your own backend buys full control over the runtime contract and compile speed, and costs a multi-year project per architecture — the trade Go made, see [[go-pipeline]].
  • Emitting C as the target language gives you every C compiler's optimizer for free and costs you control over semantics, debugging and anything C cannot express — a strategy several languages have used successfully.

See it for yourself

The flag, dump or tool that shows you this directly.

  • clang -S -emit-llvm -o - file.c prints the IR a real frontend produces; add -O2 and diff the two to see the middle-end's entire contribution.
  • opt -passes=... -S file.ll runs a chosen pass pipeline over IR by hand, which is how you attribute a transformation to a pass.
  • llc file.ll -march=aarch64 runs only code generation, which separates a middle-end question from a backend one.
  • llvm-config --components lists the libraries in an installed LLVM; llvm-as, llvm-dis, llvm-link and llvm-extract manipulate modules directly.
  • Compiler Explorer with the "LLVM IR" output pane, and its LLVM opt pipeline viewer, show the IR after each pass without a local build.

Plausible wrong readings

Stated the way a confident engineer states them.

  • "LLVM compiles my code." A client of LLVM compiles your code. Which client it is decides the frontend, the diagnostics, the language semantics and half of the performance.
  • "Clang and LLVM are two names for the same thing." Clang is a C-family frontend and a tooling library. LLVM is the infrastructure it emits into, shared with a dozen unrelated frontends.
  • "If two languages both use LLVM, they will generate the same code." They generate the same code only if their frontends emit the same IR with the same attributes, which they rarely do — the attributes are where the language's guarantees live.
  • "LLVM IR is portable, so a bitcode file runs anywhere." It encodes target-specific decisions — type sizes, ABI-driven parameter lowering, target intrinsics — from the moment the frontend emits it.
  • "Upgrading LLVM is a routine dependency bump." For a program that links its libraries it is an API migration, by policy, every release.

Misconceptions

The claim, and what is actually true.

LLVM replaced GCC.
Both are actively developed, both are used at enormous scale, and each is the default on different platforms. They are two mature toolchains with different architectures and different licences.
LLVM IR is a universal, language-independent representation.
It is a low-level, C-influenced model. Languages with precise garbage collection, exact exception semantics or unusual memory models all have to work to fit into it, which is part of why MLIR exists.
Using LLVM means you do not have to write a compiler.
It means you do not have to write an optimizer or a code generator. The frontend, the driver, the runtime, the debugger integration and the diagnostics are still yours.
Bitcode is a portable executable format.
It is a serialisation of the IR, and the IR already contains target-specific decisions the frontend made. It is portable between LLVM versions within a window, not between machines.

Go deeper

The same idea at increasing depth. Stop wherever it stops being useful.

overview

LLVM is a toolbox for people building compilers, not a compiler you run. The valuable parts are a well-defined format for describing a program and a large collection of code that improves programs in that format and turns them into machine instructions. Clang, rustc and Swift all use it, which is why they can target so many processors without each writing that code themselves.

practical

When you read a bug report or a release note, work out which component it is about: a frontend issue is Clang or rustc, an optimization issue is the middle-end and is shared by every language, and a wrong-instruction issue is a target backend and affects only that architecture. clang -S -emit-llvm at -O0 and -O2 and a diff answers most "why is this slow" questions, and opt -passes= narrows it to a pass.

advanced

The design insight worth carrying is that the reusable thing is the *representation*, not the code. Libraries are reusable because they all agree on one well-specified data structure with a verifier, so a pass can be written, tested and shared without knowing anything about the frontend that produced its input or the target that will consume its output. Everything else follows: the M+N economics, the ability to reuse the middle-end for a query JIT, the pass manager's analysis caching. MLIR is the same insight applied one level up — instead of one IR for everyone, a framework for defining many IRs with a shared infrastructure for lowering between them, which is the answer to the observation that LLVM IR is too low-level to be the right first target for machine-learning graphs or hardware description.

How much this depends on

Nothing in this domain is true of every compiler. These say how much.

implementationEverything structural here — which subprojects exist, what is in the monorepo, what the pass manager looks like — describes LLVM around 2024–2025 and has changed repeatedly. The pass manager itself was replaced (the "new pass manager" became the default in LLVM 13), MLIR and Flang were added years after the project started, and the original name was an acronym the project formally dropped.
implementationThe C++ API is unstable by explicit policy and changes every major release, roughly twice a year; bitcode is readable by newer LLVM within a supported window of prior versions; textual IR carries no compatibility promise at all. Confusing the three is how a project ends up with a test fixture that expires.
typicalThe claim that LLVM produces competitive code is true across mainstream architectures and workloads and is not universal: GCC wins on some code and some targets, vendor toolchains beat both on their own accelerators, and for compile-speed-sensitive builds a lighter backend such as Cranelift is the better engineering choice despite generating slower code.

If you were asked this in an interview

  • What is LLVM, precisely? Answer without using the word "compiler" as the noun.
  • Two languages both emit LLVM IR. Why might one produce much better code than the other?
  • What compatibility does LLVM actually promise, and what does that mean for a project that links against it?

Connections

Computer Architectureinstruction-set-architecture
Performancebenchmarking
Domains that do not exist yet
  • DevOps / Production Engineering — Pinning and upgrading a toolchain across a fleet of builds
    An unstable API on a twice-yearly cadence is a build-infrastructure problem before it is a compiler one: which version every build uses, how upgrades roll out, and how bitcode compatibility windows constrain them are owned there.