LLVM vs. QBE
QBE and LLVM are both compiler backends that utilize a Static Single-Assignment (SSA) form for intermediate representation. However, they are designed with fundamentally different philosophies, target audiences, and technical trade-offs. LLVM is an industry-standard, feature-rich infrastructure, whereas QBE is a minimalist and hackable backend aimed at simplicity and accessibility.
Here is a detailed comparison of the two:
1. Scope, Philosophy, and Target Audience
| Factor |
QBE |
LLVM |
| Philosophy |
Aims to provide 70% of the performance of advanced compilers in 10% of the code. It prioritizes simplicity, hackability, and fast compilation. |
A comprehensive, modular, and reusable set of compiler and toolchain technologies designed for high performance and adaptability across various languages and platforms. |
| Target Audience |
Primarily aimed at amateur and hobbyist language designers, researchers, and those who want a simple, understandable backend to experiment with. |
Used in the development of industry-grade compilers (like Clang for C/C++ and Rustc for Rust), Just-In-Time (JIT) compilers, and other language-based tools. |
| Project Scale |
Intentionally small, with a codebase of around 14,000 lines of C99 and no external dependencies. |
A massive, large-scale project with a vast ecosystem of tools, libraries, and subprojects. It requires a modern C++ toolchain to build. |
The design of the Intermediate Representation (IR) is a core differentiator between the two projects.
| Factor |
QBE |
LLVM |
| Complexity & Verbosity |
Features a simple, uniform IL that is less cluttered. To simplify for the user, it allows non-SSA temporaries in its input, which it converts internally. For example, incrementing a variable can be a single instruction (%v =w add %v, 1). |
Has a more complex, low-level, and strictly-typed IR. To ease SSA construction for frontend developers, it encourages the use of stack slots (alloca), which can lead to more verbose IR (a load, add, and store for a simple increment). |
| Type System |
Uses a much lighter type system, which results in a more readable and shorter IL with fewer casts. |
Has a complex and strict type system. This is crucial for advanced optimizations but often requires frontend developers to insert numerous explicit casts, which can clutter the IR. |
| Readability |
The simpler, less verbose nature of QBE’s IR makes it easier for developers to read and debug the code generated by their frontend. |
While human-readable, LLVM IR can be very verbose due to its strict SSA form and explicit memory operations, making it more challenging to inspect manually. |
3. C ABI Compatibility
Application Binary Interface (ABI) compatibility is critical for interoperability with C libraries, and the two backends handle this responsibility differently.
| Factor |
QBE |
LLVM |
| Implementation |
Provides full C ABI compatibility as a core feature. It offers IL operations to call C functions (and be called by them) without the frontend needing to implement complex ABI logic. |
Does not provide C ABI compatibility out-of-the-box. The responsibility for “lowering” function calls to match the target ABI falls on the language frontend. |
| Developer Burden |
Significantly reduces the burden on the language designer, as implementing a complete C ABI (especially for struct arguments and returns) is notoriously difficult and error-prone. |
This is a well-known issue in the LLVM community, leading to a significant amount of duplicated effort and bugs as each frontend (like Rust, Swift, etc.) must reimplement this logic. |
4. Ease of Use and Learning Curve
| Factor |
QBE |
LLVM |
| Onboarding |
Described as “riding a bicycle.” Its small codebase and simple C99 implementation make it highly “hackable” and easy to understand and modify. The tool takes a text file as input and outputs a text-based assembly file, which is straightforward to integrate into a build process. |
Often compared to “hauling your backpack with a truck.” The learning curve is steep due to its vast API, C++ implementation, and the sheer scale of the project. Writing a backend for a new target is considered a significant challenge. |
| Documentation & Community |
The project has clear documentation for its IL, but the community is much smaller. |
Has extensive documentation, tutorials (like the Kaleidoscope language tutorial), and a large, active community. However, finding answers to specific, complex questions can still be difficult. |
| Build Process |
Compiles in seconds without dependencies. |
Building LLVM is a lengthy process that can consume significant system resources. Linking against LLVM libraries can also result in slow build times for your own compiler. |
| Factor |
QBE |
LLVM |
| Optimization Goals |
Focuses on implementing optimizations that yield the most significant performance gains for the least amount of code—the “first 70%.” This includes passes like copy propagation, sparse conditional constant propagation, and dead code elimination. |
Aims for state-of-the-art performance, implementing a vast array of advanced and aggressive optimization passes that target the “last 30%” of performance. |
| Real-World Performance |
While the goal is 70% of LLVM’s performance, some user benchmarks have shown it to be closer to 50% for certain CPU-bound tasks. Performance is good enough for many uses but not intended to compete at the highest level. |
Generally considered the gold standard for performance in open-source compilers, producing highly optimized machine code for a wide variety of architectures. |
6. Supported Architectures and Output
| Factor |
QBE |
LLVM |
| Target Architectures |
Supports a limited but practical set of modern 64-bit architectures, including amd64 (Linux, macOS), arm64, and riscv64. Notably, it lacks support for 32-bit targets. |
Supports a very wide range of architectures, from common ones like x86 and ARM to more specialized ones like PowerPC and WebAssembly. |
| Output Format |
Emits human-readable, text-based assembly files and relies on a system-installed assembler (like as or nasm) to produce object files. |
Has backends that directly produce object files, bypassing the need for an external assembler. |
Summary Table
| Feature |
QBE |
LLVM |
| Primary Goal |
Simplicity, hackability, ease of use |
High performance, modularity, industry standard |
| Target User |
Hobbyist, student, researcher |
Professional compiler developers, large projects |
| C ABI Support |
Built-in |
Frontend’s responsibility |
| IR Style |
Simple, less verbose, flexible SSA |
Complex, strict SSA, verbose memory ops |
| Learning Curve |
Low |
Very High |
| Project Size |
~14 kloc C99, no dependencies |
Massive C++ project with many submodules |
| Performance |
Good (“the first 70%”) |
Excellent (state-of-the-art) |
| Architecture Support |
Limited (amd64, arm64, riscv64) |
Extensive |
| Output |
Text-based assembly |
Object files |
In conclusion, the choice between QBE and LLVM depends entirely on the project’s goals. If the aim is to learn about compilers, rapidly prototype a new language, or create a tool where ultimate performance is secondary to simplicity and maintainability, QBE is an excellent choice. For creating a production-ready, high-performance language that needs to be competitive with established languages like C++ or Rust, the power, extensive feature set, and mature ecosystem of LLVM are indispensable.
Page last modified: 2025-10-04 11:51:12