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.
People keep saying "LLVM" as though it were one program — what is it actually?
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.
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
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.
| Component | What it is | Who uses it |
|---|---|---|
| LLVM Core | The IR data structures, verifier, pass infrastructure, analyses and optimizations | Every client, always |
| Code generation | Target descriptions, instruction selection, scheduling and register allocation per architecture | Any client emitting machine code rather than IR |
| Clang | A C, C++, Objective-C and CUDA frontend, plus a driver and a tooling library | C-family users; and anything reusing its AST — see [[clang]] |
| LLD | A linker for ELF, Mach-O, COFF and WebAssembly | Builds that want a faster link than the system linker |
| MLIR | A framework for defining *your own* IRs and lowering between them | Machine-learning and domain-specific compilers |
| compiler-rt, libc++, libunwind | Runtime support: builtins, sanitizers, a standard library, unwinding | Programs built by Clang; the sanitizers, by anything |
| LLDB | A debugger built on Clang's expression parser and type system | Anyone 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
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.cprints the IR a real frontend produces; add-O2and diff the two to see the middle-end's entire contribution.opt -passes=... -S file.llruns a chosen pass pipeline over IR by hand, which is how you attribute a transformation to a pass.llc file.ll -march=aarch64runs only code generation, which separates a middle-end question from a backend one.llvm-config --componentslists the libraries in an installed LLVM;llvm-as,llvm-dis,llvm-linkandllvm-extractmanipulate 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.
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.
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
- DevOps / Production Engineering — Pinning and upgrading a toolchain across a fleet of buildsAn 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.