<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Reginald Ojunga</title>
    <description>The latest articles on DEV Community by Reginald Ojunga (@kwachojunga).</description>
    <link>https://dev.to/kwachojunga</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1602391%2F1a7a824b-fa65-4673-8148-0f1f39d209ea.jpeg</url>
      <title>DEV Community: Reginald Ojunga</title>
      <link>https://dev.to/kwachojunga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kwachojunga"/>
    <language>en</language>
    <item>
      <title>NASA Power of 10 rules</title>
      <dc:creator>Reginald Ojunga</dc:creator>
      <pubDate>Thu, 10 Sep 2026 08:53:21 +0000</pubDate>
      <link>https://dev.to/kwachojunga/nasa-power-of-10-rules-3fc6</link>
      <guid>https://dev.to/kwachojunga/nasa-power-of-10-rules-3fc6</guid>
      <description>&lt;p&gt;So if you have been in the programming space long enough, you have&lt;br&gt;
probably heard of Nasa's Power of 10 rules.&lt;br&gt;
And these are basically a set of rules that lower chances of software&lt;br&gt;
failure in mission critical software.&lt;/p&gt;

&lt;p&gt;The first is simplify control flow. This isn't particularly an issue especially for those using current high level languages. I frankly do not know what the fuss is all about on gotos.&lt;/p&gt;

&lt;p&gt;Provide an explicit iteration upper bound. Prevent the software from entering into an endless loop.&lt;/p&gt;

&lt;p&gt;No dynamic memory allocation after initialization. I am uncertain how programs that perform strict static allocation handle inputs at runtime. On the upside it obviously makes sense a dynamic allocation translates to a syscall. You want to avoid those if you are trying to be fast.&lt;/p&gt;

&lt;p&gt;Use short functions. This i believe is to help in developer experience by reducing complexity.&lt;/p&gt;

&lt;p&gt;Use assertions. Assertions come in a set that involve either checking the value, property of the value or the value's location in memory. These are some of the ways of checking for equivalence.&lt;br&gt;
And basically the purpose of assertions is to act as in-flight checks. They add a layer of redundancy.&lt;/p&gt;

&lt;p&gt;Declare all data at the smallest scale possible. I believe this speaks to the need for performance. Smaller data structures are almost equivalent to what may be deemed as cache-conscious data structures.&lt;br&gt;
They are good for both spatial and temporal locality and as a consequence they prevent cache thrashing.&lt;/p&gt;

&lt;p&gt;Check the return value of all non-void functions. I think this can be done using assertions.&lt;/p&gt;

&lt;p&gt;Limit pointer use. No more than one dereference. I think extensive use of pointers in any given program&lt;br&gt;
is equivalent to playing russian roulette.&lt;/p&gt;

&lt;p&gt;Finally always compile with all warnings enabled.&lt;/p&gt;

&lt;p&gt;Most of these rules have been adopted and used to build different development cultures like Tiger Style.&lt;br&gt;
Things like static allocation have been used to explain tigerbeetle's high performance.&lt;/p&gt;

&lt;p&gt;There's a full length video on this by &lt;a href="https://www.youtube.com/watch?v=GRJtYwneG2Q" rel="noopener noreferrer"&gt;Gerald Holzman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>software</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Inside LLVM: Engineering a Target-Independent Compiler(3)</title>
      <dc:creator>Reginald Ojunga</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:14:05 +0000</pubDate>
      <link>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler-1ce5</link>
      <guid>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler-1ce5</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Part 3 — Reusing Optimizations Across Architectures&lt;/strong&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Middle-End&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Question
&lt;/h3&gt;

&lt;p&gt;Parts 1 and 2 established two foundational abstractions. LLVM is organized as a collection of&lt;br&gt;
reusable libraries rather than a monolithic compiler, and LLVM IR serves as the stable contract &lt;br&gt;
that lets many languages feed a single optimizer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I hope it is clear by now what the IR is. It is just a data structure that eases the process of reorganizing code structure into what its equivalent hand-written assembly would look like. This means that ideally performing optimizations on the written code is not particularly expensive. In the next few sections, this will be illustrated, hopefully without bringing about much confusion. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A new question now arises.&lt;/p&gt;

&lt;p&gt;If the same IR is produced by frontends as different as Clang, Rustc, and the Swift compiler, why&lt;br&gt;
should the &lt;em&gt;same&lt;/em&gt; sequence of optimizations improve all of them? More precisely:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why can the same optimization improve programs written in completely different languages and still produce good code for completely different processors?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer lies in what the middle-end (the contract - LLVM IR) abstracts away.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Limitation
&lt;/h3&gt;

&lt;p&gt;Once a program has been lowered to LLVM IR, a large collection of analyses and transformations &lt;br&gt;
become possible: &lt;br&gt;
dead-code elimination, common-subexpression elimination, sparse conditional constant propagation,&lt;br&gt;
loop-invariant code motion, inlining, vectorization, and many others.&lt;/p&gt;

&lt;p&gt;If each of these transformations had to be written with intimate knowledge of a particular &lt;br&gt;
language or a particular micro-architecture, the reuse promised by Parts 1 and 2 would collapse. &lt;br&gt;
An inliner that understood Rust’s ownership semantics would be useless for C. A loop optimizer that &lt;br&gt;
hard-coded the characteristics of an Intel Golden Cove branch predictor would generate poor code &lt;br&gt;
for an ARM Neoverse core or a simple in-order RISC-V microcontroller.&lt;/p&gt;

&lt;p&gt;This therefore imposes a new engineerining hurdle:&lt;/p&gt;

&lt;p&gt;The IR has to be sufficiently composable to allow the middle-end to improve code &lt;em&gt;without&lt;/em&gt; knowing the source language that produced the IR and &lt;em&gt;without&lt;/em&gt; &lt;br&gt;
knowing most of the micro-architectural details of the processor that will eventually execute it.&lt;/p&gt;

&lt;p&gt;Traditional optimizers often failed this test. Many were deeply entangled with the frontend that fed them &lt;br&gt;
or with the backend they fed. The result was duplicated effort and optimizations that were not portable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The New Abstraction
&lt;/h3&gt;

&lt;p&gt;LLVM’s response is a suite of analyses and transformations that operate almost &lt;br&gt;
exclusively on the properties of LLVM IR itself, together with a narrow, carefully controlled set of target &lt;br&gt;
queries.&lt;/p&gt;

&lt;p&gt;The optimizer is allowed to rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLVM-IR SSA property,&lt;/li&gt;
&lt;li&gt;the explicit control-flow graph as exposed by &lt;code&gt;BasicBlocks&lt;/code&gt; and &lt;code&gt;TerminatorInstructions&lt;/code&gt;, &lt;/li&gt;
&lt;li&gt;the type system,&lt;/li&gt;
&lt;li&gt;the use-def chains,&lt;/li&gt;
&lt;li&gt;and a small number of target descriptors (DataLayout, TargetLibraryInfo, TargetTransformInfo).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is &lt;em&gt;not&lt;/em&gt; allowed to assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a particular finite register file,&lt;/li&gt;
&lt;li&gt;a particular pipeline depth or issue width,&lt;/li&gt;
&lt;li&gt;the presence or accuracy of a branch predictor,&lt;/li&gt;
&lt;li&gt;a particular memory-reordering model,&lt;/li&gt;
&lt;li&gt;or the existence of speculative execution with specific side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assumptions that are implementation specific.&lt;/p&gt;

&lt;p&gt;By relying on a general infrastructure (LLVM-IR itself), the same pass can run on IR that originated in C, Rust,&lt;br&gt;
or Swift and later be lowered to x86-64, AArch64, or RISC-V.&lt;/p&gt;

&lt;p&gt;I think it is quite accurate to say that the IR explicitly refuses to rely on &lt;br&gt;
specific information unless it is absolutely neccessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. How It Works
&lt;/h3&gt;

&lt;p&gt;The middle-end is organized as a pipeline of &lt;strong&gt;passes&lt;/strong&gt;. Each pass is either an analysis &lt;br&gt;
(computing information such as dominator trees, loop nests, or alias sets) or a transformation &lt;br&gt;
(rewriting the IR while preserving semantics). These are source to source transformations.&lt;br&gt;
They modify LLVM-IR.&lt;/p&gt;

&lt;p&gt;Modern LLVM uses the New Pass Manager. Analyses are requested on demand and cached; transformations &lt;br&gt;
declare which analyses they preserve or invalidate. This machinery lets the compiler avoid recomputing&lt;br&gt;
expensive results and keeps the pipeline composable.&lt;/p&gt;

&lt;p&gt;Two further design decisions keep the middle-end largely target-independent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canonicalization&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Many passes exist solely to drive the IR toward a smaller set of preferred forms. InstCombine, SimplifyCFG, &lt;br&gt;
and related passes rewrite instruction sequences into canonical patterns so that later passes see less &lt;br&gt;
surface variation. Canonical forms are chosen because they are easier to reason about, not because they &lt;br&gt;
match any particular machine. They are equivalents of irreducible facts at the instruction level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controlled target queries&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When a pass truly needs hardware knowledge—most often for cost modeling—it consults TargetTransformInfo (TTI) or&lt;br&gt;
TargetLibraryInfo (TLI). Vectorizers ask TTI about legal vector widths and the relative cost of shuffles. The inliner &lt;br&gt;
may ask about call overhead. These queries are deliberate, narrow leaks; the great majority of transformations never &lt;br&gt;
make them. These are the abstraction leaks spoken of in the &lt;a href="https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler1-30li/"&gt;first phase&lt;/a&gt; of the series.&lt;/p&gt;

&lt;h4&gt;
  
  
  What the IR Abstracts Away
&lt;/h4&gt;

&lt;p&gt;LLVM IR presents a simplified model of execution that deliberately erases many hardware realities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infinite virtual registers.&lt;/strong&gt; Real processors have small, architecturally visible register files and must spill. 
The middle-end largely ignores this constraint; register allocation occurs later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple control flow.&lt;/strong&gt; IR has basic blocks and terminators. It does not expose branch-predictor tables, 
return-address stacks, or the speculative execution machinery that modern CPUs use to keep pipelines full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential consistency within a thread (with explicit atomics).&lt;/strong&gt; Real machines reorder loads and stores. 
x86 provides a relatively strong model; ARM and RISC-V provide weaker models that require more barriers. 
The middle-end works with the IR’s memory model and leaves the insertion of the precise fences to later stages
or to target-specific lowering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uniform instruction costs.&lt;/strong&gt; An &lt;code&gt;add&lt;/code&gt; in IR is just an &lt;code&gt;add&lt;/code&gt;. On a real machine its latency, throughput, 
and ability to execute on multiple ports vary dramatically. Only cost-model-driven passes consult TTI 
for this information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Concrete Architectural Differences the Middle-End Mostly Ignores
&lt;/h4&gt;

&lt;p&gt;Consider three capabilities that hardware exposes to varying degrees and that aggressive programmers&lt;br&gt;
sometimes exploit directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch prediction and speculative execution&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
High-performance x86 and ARM cores invest heavily in predictors and execute far beyond&lt;br&gt;
unresolved branches. &lt;br&gt;
A misprediction is expensive, so the shape of control flow matters. Some code bases &lt;br&gt;
are hand-tuned to make branches more predictable or to use conditional moves to avoid them. &lt;br&gt;
LLVM IR has no first-class notion of “this branch is highly predictable” or “this value was produced by speculation.”&lt;br&gt;
The middle-end may convert branches into selects (or vice versa) based on simple heuristics &lt;br&gt;
and TTI cost models, but it does not try to model predictor state. The final decision about &lt;br&gt;
branch versus conditional move is left to the backend, where target-specific knowledge is available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instruction reordering and out-of-order execution&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Out-of-order cores (most modern x86 and high-end ARM designs) dynamically reorder independent instructions to &lt;br&gt;
hide latency. In-order cores (many embedded RISC-V implementations, older ARM cores) do not. &lt;br&gt;
The middle-end performs &lt;em&gt;static&lt;/em&gt; reordering only when it is profitable under a simplified cost model; &lt;br&gt;
it does not attempt to schedule for a particular out-of-order window or reservation-station&lt;br&gt;
configuration. Those decisions belong to the machine scheduler, which runs after instruction&lt;br&gt;
selection and has access to detailed pipeline models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit software pipelining or prefetching&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Some architectures and some performance-sensitive code sequences rely on software pipelining or&lt;br&gt;
on explicit prefetch instructions. LLVM IR has no direct encoding of “this load should be issued&lt;br&gt;
two iterations early.” Loop transformations may create the conditions under which a later backend&lt;br&gt;
can software-pipeline, but the middle-end itself does not emit architecture-specific prefetch&lt;br&gt;
intrinsics unless a pass has been told (via TTI or target hooks) that they are profitable.&lt;/p&gt;

&lt;p&gt;In each case the IR and the middle-end provide a portable substrate. Such features that differ most&lt;br&gt;
across micro-architectures are relegated to later stages and often accessed through narrow interfaces, or are left entirely&lt;br&gt;
to the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Design Trade-offs
&lt;/h3&gt;

&lt;p&gt;This abstraction of hardware details buys massive reuse: the same LoopVectorize pass, the same GVN, &lt;br&gt;
the same inliner, improve C, Rust, and Swift alike, and the resulting IR can be handed to any LLVM&lt;br&gt;
backend. Even extensive polyhedral optimizations that improve data locality exposing pockets of parrallelism&lt;br&gt;
become possible for a whole set of architectures.&lt;/p&gt;

&lt;p&gt;Obviously, the cost is that some optimization opportunities are left on the table until later stages,&lt;br&gt;
and a few must be handled by target-specific passes. A purely target-agnostic middle-end cannot,&lt;br&gt;
for example, know that a particular sequence will saturate a specific execution port on a&lt;br&gt;
particular CPU, nor can it know the exact misprediction penalty of a given branch.&lt;/p&gt;

&lt;p&gt;LLVM accepts this trade-off because the alternative—writing and maintaining separate optimization pipelines for every&lt;br&gt;
language and every major micro-architecture re-introduces the N × M problem that Part 1 set out to describe.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The Abstraction Leaks
&lt;/h3&gt;

&lt;p&gt;The middle-end is only &lt;em&gt;mostly&lt;/em&gt; target-independent. Three important leaks exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DataLayout&lt;/strong&gt; — already present in the IR (Part 2). Alignment and pointer-size decisions affect many optimizations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TargetLibraryInfo&lt;/strong&gt; — tells the optimizer which library calls exist and what they mean (e.g., whether &lt;code&gt;memcpy&lt;/code&gt; may be
assumed, or whether a math function is available).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TargetTransformInfo&lt;/strong&gt; — the primary channel for cost modeling. Vectorization, inlining heuristics, and some
simplification decisions consult it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, a few passes are allowed to behave differently when they know they are compiling for a particular target, &lt;br&gt;
and some language frontends inject target-aware attributes early. These leaks are intentional. They let the optimizer &lt;br&gt;
remain useful on real hardware without forcing every transformation to become target-specific.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Source Tour
&lt;/h3&gt;

&lt;p&gt;The middle-end lives primarily in three places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llvm/lib/Transforms/&lt;/code&gt; — the transformations themselves (Scalar, Vectorize, IPO, Utils, \ldots)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llvm/lib/Analysis/&lt;/code&gt; — analyses that transformations query (LoopInfo, AliasAnalysis, ScalarEvolution, \ldots)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llvm/lib/Passes/&lt;/code&gt; — the New Pass Manager infrastructure and the pipelines that assemble analyses and 
transformations into &lt;code&gt;-O2&lt;/code&gt;, &lt;code&gt;-O3&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Target queries surface through headers in &lt;code&gt;llvm/include/llvm/Analysis/&lt;/code&gt; (especially TargetTransformInfo) and are &lt;br&gt;
implemented by each backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Looking Ahead
&lt;/h3&gt;

&lt;p&gt;The middle-end can now improve IR without knowing the original language and without knowing most &lt;br&gt;
micro-architectural details. The IR, however, still describes an ideal machine: infinite &lt;br&gt;
registers, uniform instructions, and simple control flow.&lt;/p&gt;

&lt;p&gt;Real processors have finite registers, various specific types and operations, complex &lt;br&gt;
calling conventions, and idiosyncratic instruction encodings. The next engineering problem is&lt;br&gt;
therefore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does LLVM turn ideal IR instructions into legal machine instructions for many different ISAs without forcing the middle-end to know about those ISAs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That problem forces the introduction of instruction selection and Machine IR — the subject of Part 4.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Principle #3 — Optimize Against a Simplified Model
&lt;/h3&gt;

&lt;p&gt;When many different hardware implementations must be supported, perform the bulk of optimization&lt;br&gt;
against a simplified, mostly hardware-agnostic model, and consult detailed target information only&lt;br&gt;
through narrow, explicit interfaces.&lt;/p&gt;

&lt;p&gt;LLVM IR erases finite registers, pipeline structure, branch-predictor behavior, and most&lt;br&gt;
memory-reordering details so that the same passes can serve many languages and many architectures.&lt;br&gt;
The few facts that must be known are obtained through DataLayout, TTI, and TLI. Everything else is&lt;br&gt;
deferred until the backend, where target-specific knowledge is unavoidable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Series Thread
&lt;/h3&gt;

&lt;p&gt;Every abstraction in LLVM exists because the previous one could not adequately contain a particular form of complexity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The monolithic compiler could not solve the N × M problem → libraries (Part 1).
&lt;/li&gt;
&lt;li&gt;ASTs and assembly could not serve as a shared medium → LLVM IR (Part 2).
&lt;/li&gt;
&lt;li&gt;Language-specific or architecture-specific optimizers could not be reused → a middle-end that operates 
on a simplified, mostly target-independent model of the IR (Part 3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next abstraction will confront the fact that this simplified model must eventually be &lt;br&gt;
reconciled with the chaotic reality of actual instruction sets.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>computerscience</category>
      <category>software</category>
    </item>
    <item>
      <title>Inside LLVM: Engineering a Target-Independent Compiler(2)</title>
      <dc:creator>Reginald Ojunga</dc:creator>
      <pubDate>Fri, 14 Aug 2026 04:56:31 +0000</pubDate>
      <link>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler2-jgp</link>
      <guid>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler2-jgp</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 2 — LLVM IR: Designing a Universal Contract&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The Interface Between Languages and Machines&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The Question
&lt;/h3&gt;

&lt;p&gt;In Part 1 we established why LLVM was organized as a collection of reusable libraries.&lt;br&gt;
The primary objective was to find a way to at least reduce the need to always reinvent the wheel.&lt;br&gt;
A new language no longer needs to invent its own optimizer, and a new architecture no longer needs to reimplement an entire compiler.&lt;/p&gt;

&lt;p&gt;That architecture, however, rests on a single, critical assumption:&lt;/p&gt;

&lt;p&gt;The existence of a common representation that every frontend can produce and&lt;br&gt;
that the shared optimizer and backends can consume.(so the IR is load bearing to its success)&lt;br&gt;
In the absence of such a representation, the library boundaries described in&lt;br&gt;
Part 1 would be meaningless. Each frontend would still be forced to speak a private language to the rest of the system.&lt;/p&gt;

&lt;p&gt;The central question of this article is therefore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can Clang, Rust, Swift, Zig, and many other languages all feed the same optimizer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is LLVM IR — a carefully engineered software contract.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The Limitation
&lt;/h3&gt;

&lt;p&gt;Two natural candidates already existed long before LLVM.&lt;/p&gt;

&lt;p&gt;(a). &lt;strong&gt;Abstract Syntax Trees&lt;/strong&gt; preserve almost everything a frontend knows about the source program: &lt;br&gt;
syntactic structure, types, scopes, and language-specific semantics. They are excellent for language-specific&lt;br&gt;
analysis and transformation. They are poor candidates for a shared abstraction.&lt;br&gt;
Their use results to a fragile interface that proves constricting to language designers&lt;br&gt;
(There are only so many language grammars a single AST can represent).&lt;/p&gt;

&lt;p&gt;An AST for Rust's ownership and borrowing rules has little in common with an AST for C's &lt;br&gt;
unchecked pointers or Swift's reference counting. An optimizer written against one would be &lt;br&gt;
nearly useless for the others. The N × M problem would simply reappear at the level of tree walks and type checkers.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the source/AST level, the compiler still sees language concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FunctionDecl
└── name: add_one
    return type: int
    parameter:
        └── x : int
    body:
        └── ReturnStmt
            └── BinaryOperator '+'
                ├── DeclRefExpr: x
                └── IntegerLiteral: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It knows that this is a C function, that &lt;code&gt;x&lt;/code&gt; is an &lt;code&gt;int&lt;/code&gt;, that &lt;code&gt;+&lt;/code&gt; is a source-level &lt;br&gt;
addition expression, and that the expression is the operand of a &lt;code&gt;return&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note: This example may not clearly illustrate the advantages of LLVM IR over ASTs&lt;br&gt;
because of the stable way in which function structures are conceptualized across different &lt;br&gt;
languages. But try thinking of language specific contexts. For instance, the various ways in which &lt;br&gt;
object oriented languages model shared behavior. (Just pick a language specific context)&lt;/p&gt;

&lt;p&gt;In contrast, LLVM IR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;entry:&lt;/span&gt;
  &lt;span class="nv"&gt;%add&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%add&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no &lt;code&gt;ReturnStmt&lt;/code&gt;, no &lt;code&gt;BinaryOperator&lt;/code&gt; AST node, and no C-specific notion of an &lt;code&gt;int&lt;/code&gt; declaration.&lt;br&gt;
This means the constrain to neccessarily use the return keyword is lifted.(Of course, the space with&lt;br&gt;
which one can describe what it means for a function to have a return value is limited but&lt;br&gt;
at least it affords the authors a way to describe what it means for a function to have a return &lt;br&gt;
value without being tied to a keyword or notation).&lt;/p&gt;

&lt;p&gt;Programming languages are avenues to think about what is underneath.&lt;/p&gt;

&lt;p&gt;The important point is not that LLVM IR contains &lt;em&gt;less information&lt;/em&gt;. It contains &lt;strong&gt;different information&lt;/strong&gt;: source-language structure has been replaced by explicit computation.&lt;/p&gt;

&lt;p&gt;To further illustrate:&lt;/p&gt;

&lt;p&gt;Consider a language level construct as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rust compiler can reason about properties that are meaningful at the Rust level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function
├── name: increment
├── parameter: x : i32
├── return type: i32
└── expression:
    └── Add
        ├── x
        └── integer literal 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AST can also retain language-specific information such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ownership
borrowing
lifetimes
generic parameters
traits
pattern matching
source locations
visibility
attributes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an oversimplified way of potraying rustc.&lt;/p&gt;

&lt;p&gt;An optimization pass that has to generalise across mulitple target architectures has no need &lt;br&gt;
to grasp the language-specific details of the source code.&lt;/p&gt;

&lt;p&gt;(b). &lt;strong&gt;Raw assembly or machine code&lt;/strong&gt; sits at the opposite extreme. It is already target-specific. Register names,&lt;br&gt;
instruction encodings, calling conventions, and ABI details are baked in. An optimizer working at this level&lt;br&gt;
cannot be shared across architectures, and many high-level opportunities (inlining across language boundaries,&lt;br&gt;
high-level loop transformations, language-independent alias analysis) have already been lost.&lt;/p&gt;

&lt;p&gt;What was missing was a representation that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low enough to model real computation efficiently,&lt;/li&gt;
&lt;li&gt;high enough to support powerful, language-independent optimizations,&lt;/li&gt;
&lt;li&gt;stable enough to serve as a long-lived interface between many independent projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither ASTs nor assembly satisfied that combination of requirements. A new abstraction was required&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Enter LLVM IR
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8uwc3gucab4kocmq91p6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8uwc3gucab4kocmq91p6.png" alt="LLVMIR-chart" width="784" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLVM IR is that abstraction.&lt;/p&gt;

&lt;p&gt;It occupies the narrow design space between language-specific trees and machine-specific code. &lt;br&gt;
Frontends lower their ASTs into LLVM IR; the middle-end optimizes that IR without knowing which&lt;br&gt;
language produced it; backends later lower the optimized IR into Machine IR and ultimately machine&lt;br&gt;
code.&lt;/p&gt;

&lt;p&gt;Consider our initial function after lowering to a target architecture.&lt;/p&gt;

&lt;p&gt;LLVM IR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@add_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;entry:&lt;/span&gt;
  &lt;span class="nv"&gt;%add&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%add&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On an x86-64 System V target, the corresponding assembly can be as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_one:
    lea     eax, [rdi + 1]
    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On AArch64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_one:
    add     w0, w0, #1
    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On RISC-V:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_one:
    addi    a0, a0, 1
    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important observation is that the &lt;strong&gt;LLVM IR is the same&lt;/strong&gt;, while the machine instructions differ.&lt;/p&gt;

&lt;p&gt;The conceptual stack is therefore:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnz9f6a6l7mf2l1hbrg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnz9f6a6l7mf2l1hbrg1.png" alt="New Representation" width="241" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thus, LLVM IR is deliberately not a programming language in the conventional sense. It is a &lt;br&gt;
&lt;strong&gt;contract&lt;/strong&gt; that defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what values exist,&lt;/li&gt;
&lt;li&gt;how control flows,&lt;/li&gt;
&lt;li&gt;what operations are expressible,&lt;/li&gt;
&lt;li&gt;and what properties the optimizer may rely upon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a frontend has emitted valid LLVM IR, it has fulfilled its primary obligation to the rest of the infrastructure.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. The IR structure
&lt;/h3&gt;

&lt;p&gt;Before we get to describing the structure consider the following instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;abs_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AST expresses the source structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function
└── IfStmt
    ├── condition: x &amp;lt; 0
    ├── then:
    │   └── return -x
    └── else:
        └── return x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLVM IR representation of the &lt;code&gt;abs_value&lt;/code&gt; function makes the control-flow structure explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@abs_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;entry:&lt;/span&gt;
  &lt;span class="nv"&gt;%cmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;icmp&lt;/span&gt; &lt;span class="k"&gt;slt&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;i1&lt;/span&gt; &lt;span class="nv"&gt;%cmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%negative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%positive&lt;/span&gt;

&lt;span class="nl"&gt;negative:&lt;/span&gt;
  &lt;span class="nv"&gt;%neg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sub&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%done&lt;/span&gt;

&lt;span class="nl"&gt;positive:&lt;/span&gt;
  &lt;span class="k"&gt;br&lt;/span&gt; &lt;span class="kt"&gt;label&lt;/span&gt; &lt;span class="nv"&gt;%done&lt;/span&gt;

&lt;span class="nl"&gt;done:&lt;/span&gt;
  &lt;span class="nv"&gt;%result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;phi&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;%neg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%negative&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%positive&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the optimizer can see an explicit control-flow graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hrnny7d4j4hm0r2wlqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hrnny7d4j4hm0r2wlqh.png" alt="explicit-cfg" width="317" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;phi&lt;/code&gt; instruction is important here. It represents the SSA value selected&lt;br&gt;
according to which predecessor block was taken. LLVM's language reference &lt;br&gt;
specifies &lt;code&gt;phi&lt;/code&gt; precisely in terms of the predecessor blocks of the current basic block.&lt;/p&gt;

&lt;p&gt;This is something an optimizer benefits from having &lt;strong&gt;explicitly represented&lt;/strong&gt; rather than having to &lt;br&gt;
rediscover from source-level syntax.&lt;/p&gt;

&lt;p&gt;The previous IR contains two control-flow paths.&lt;/p&gt;

&lt;p&gt;But the optimizer may recognize that the computation can be expressed using a conditional move or equivalent target instruction.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        x &amp;lt; 0 ?
       /       \
    -x           x
       \       /
          result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On x86-64, one possible optimized result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abs_value:
    mov     eax, edi
    neg     eax
    cmovns  eax, edi
    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So to further refine the nature of the pipeline, what we currently have is;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphgrn0mz9ltbo0dzc53z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphgrn0mz9ltbo0dzc53z.png" alt=" " width="276" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the heart of the IR are a small number of interlocking concepts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Values, Users, and Use-Def Chains&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Every computed result is a &lt;code&gt;Value&lt;/code&gt;. Every place that consumes a value is a &lt;code&gt;User&lt;/code&gt;. The edges between them&lt;br&gt;
form use-def and def-use chains. These chains are the primary data structure that almost every analysis and&lt;br&gt;
transformation walks. Because the IR is in SSA&lt;sup id="fnref2"&gt;2&lt;/sup&gt; form, each value has a single definition, which&lt;br&gt;
dramatically simplifies many algorithms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modules, Functions, Basic Blocks, and Instructions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A &lt;code&gt;Module&lt;/code&gt; is the top-level container (roughly a translation unit). It contains &lt;code&gt;Function&lt;/code&gt;s. &lt;br&gt;
Each function contains &lt;code&gt;BasicBlock&lt;/code&gt;s. Each basic block contains a linear sequence of &lt;br&gt;
&lt;code&gt;Instruction&lt;/code&gt;s that ends in a terminator. This hierarchical structure gives optimizers clear units&lt;br&gt;
of work and clear control-flow graphs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Type System&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
LLVM IR is strongly typed. Integer types of arbitrary bit width, floating-point types, pointers, arrays,&lt;br&gt;
structs, vectors, and function types are all first-class. The type system is deliberately low-level; &lt;br&gt;
it does not encode high-level language notions such as classes, ownership, or generics. Those concepts must&lt;br&gt;
be lowered by the frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DataLayout&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
While the IR tries to remain target-independent, the physical layout of data cannot be ignored. The &lt;code&gt;DataLayout&lt;/code&gt;&lt;br&gt;
string describes pointer sizes, alignment rules, endianness, and related properties. It is the first controlled leak&lt;br&gt;
of target information into the "target-independent" IR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attributes, Metadata, and Calling Conventions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Function and parameter attributes, metadata nodes, and explicit calling-convention annotations provide the additional &lt;br&gt;
channels through which frontends and targets communicate information that does not belong in the core instruction set&lt;br&gt;
(inlining hints, aliasing properties, debug information, exception-handling tables, etc.).&lt;/p&gt;

&lt;p&gt;Together these mechanisms form a complete, self-contained intermediate language that is stable enough to serve as a&lt;br&gt;
binary interface (bitcode) and expressive enough for serious optimization.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Design Trade-offs
&lt;/h3&gt;

&lt;p&gt;LLVM IR must satisfy two opposing pressures simultaneously.&lt;/p&gt;

&lt;p&gt;It must be &lt;strong&gt;high enough&lt;/strong&gt; that the same optimizer can improve code originating from very different languages. &lt;br&gt;
High-level information that has been needlessly discarded cannot be recovered later.&lt;/p&gt;

&lt;p&gt;It must be &lt;strong&gt;low enough&lt;/strong&gt; that the eventual mapping to real hardware remains efficient. An IR that is too abstract &lt;br&gt;
forces the backend to re-discover facts that should have been explicit.&lt;/p&gt;

&lt;p&gt;The design therefore makes a series of deliberate choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSA form is required. This simplifies optimization at the cost of making the IR harder to emit directly 
from some frontends.&lt;/li&gt;
&lt;li&gt;The type system is structural and relatively simple. Rich language type systems must be erased or encoded explicitly by 
the frontend.&lt;/li&gt;
&lt;li&gt;Control flow is explicit and unstructured (basic blocks + terminators) rather than structured. This makes many analyses 
uniform at the cost of losing surface syntax.&lt;/li&gt;
&lt;li&gt;Side effects, memory, and concurrency are modeled with a relatively small set of primitive instructions plus attributes 
and metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These decisions are not inevitable; they are engineering judgments about which complexities belong in the shared middle &lt;br&gt;
and which complexities should be pushed into frontends or backends.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The Abstraction Leaks
&lt;/h3&gt;

&lt;p&gt;Even though LLVM IR is presented as target-independent, several forms of target knowledge already appear inside it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DataLayout&lt;/strong&gt; — pointer size, ABI alignment, endianness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Triple&lt;/strong&gt; — identifies the intended architecture, vendor, and operating system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calling conventions&lt;/strong&gt; — encoded as attributes because different platforms pass arguments differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address spaces&lt;/strong&gt; — required for some architectures and for certain language features (e.g., GPU memory spaces).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic memory operations&lt;/strong&gt; — whose precise semantics can depend on the target’s memory model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not accidental impurities. They exist because a purely target-agnostic IR would force either incorrect code or &lt;br&gt;
an explosion of complexity in every backend. LLVM’s pragmatic stance is that a small, well-documented set of target facts &lt;br&gt;
may appear early, provided the great majority of optimization remains independent of any particular ISA.&lt;/p&gt;

&lt;p&gt;The presence of these leaks reinforces a principle already stated in Part 1: good abstractions isolate complexity; they&lt;br&gt;
do not pretend the complexity has vanished.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Source Tour
&lt;/h3&gt;

&lt;p&gt;The core IR implementation lives in two tightly related locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llvm/include/llvm/IR/&lt;/code&gt; — public headers defining &lt;code&gt;Module&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt;, &lt;code&gt;BasicBlock&lt;/code&gt;, &lt;code&gt;Instruction&lt;/code&gt;, &lt;code&gt;Value&lt;/code&gt;, &lt;code&gt;Type&lt;/code&gt;, &lt;code&gt;DataLayout&lt;/code&gt;, attributes, metadata, and related classes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llvm/lib/IR/&lt;/code&gt; — the corresponding implementations, including the bitcode reader/writer, the assembler/disassembler for 
the textual IR, and the core data structures that maintain use-def chains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When reading this code, notice how little of it knows about any particular programming language or processor.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Looking Ahead
&lt;/h3&gt;

&lt;p&gt;We now have a common language that many frontends can speak and that a shared optimizer can understand. The next engineering question&lt;br&gt;
is immediate:&lt;/p&gt;

&lt;p&gt;Once a program has been expressed in LLVM IR, how should a collection of independent analyses and transformations be organized so &lt;br&gt;
that they remain composable, reusable, and largely target-independent?&lt;/p&gt;

&lt;p&gt;That question leads directly to the Pass Manager and the middle-end contract — the subject of Part 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Principle #2 — Define a Stable Intermediate Contract
&lt;/h3&gt;

&lt;p&gt;When many independent producers and consumers must cooperate, introduce a narrow, well-specified intermediate representation&lt;br&gt;
that both sides can rely upon.&lt;br&gt;
ASTs were too language-specific. Assembly was too machine-specific. LLVM IR was introduced to occupy the space between them and thereby make the library-based architecture of Part 1 possible. Every later abstraction in LLVM builds on the existence of this contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Series Thread
&lt;/h3&gt;

&lt;p&gt;As previously stated or at least hinted at, every abstraction in LLVM exists because the previous one could not adequately contain &lt;br&gt;
a particular form of complexity.&lt;br&gt;
The traditional monolithic compiler could not solve the N × M scaling problem → LLVM introduced the compiler-as-libraries abstraction (Part 1). &lt;br&gt;
ASTs and assembly could not serve as a shared medium between many languages and many targets → LLVM introduced LLVM IR as the universal &lt;br&gt;
contract (Part 2).&lt;/p&gt;

&lt;p&gt;The next layer will confront a new problem: how to organize dozens of independent optimizations so that they remain maintainable and reusable across all the languages that now speak this common IR.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;More information can be found from &lt;a href="https://llvm.org/pubs/2005-05-04-LattnerPHDThesis.pdf" rel="noopener noreferrer"&gt;Lattner's thesis&lt;/a&gt; and the &lt;a href="https://llvm.org/docs/" rel="noopener noreferrer"&gt;LLVM documentation&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://pfalcon.github.io/ssabook/latest/book-full.pdf" rel="noopener noreferrer"&gt;SSABook&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Inside LLVM: Engineering a Target-Independent Compiler(1)</title>
      <dc:creator>Reginald Ojunga</dc:creator>
      <pubDate>Tue, 11 Aug 2026 05:13:43 +0000</pubDate>
      <link>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler1-30li</link>
      <guid>https://dev.to/kwachojunga/inside-llvm-engineering-a-target-independent-compiler1-30li</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Why LLVM Exists&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;From Many Compilers to One Infrastructure&lt;/strong&gt;
&lt;/h3&gt;




&lt;h2&gt;
  
  
  1. The Question
&lt;/h2&gt;

&lt;p&gt;Imagine it is the late 1990s. You form part of a team that has built a successful compiler for a&lt;br&gt;
single programming language targeting a single processor architecture. The system is well&lt;br&gt;
understood, the optimizer is producing good code, and the backend reliably emits binaries &lt;br&gt;
for your target machine.&lt;/p&gt;

&lt;p&gt;Then the requirements change.&lt;/p&gt;

&lt;p&gt;A second programming language needs to be supported. Soon after, a new processor architecture becomes&lt;br&gt;
commercially important. Before long, customers expect support for multiple languages, multiple operating&lt;br&gt;
systems, and an ever-growing range of processor families.&lt;/p&gt;

&lt;p&gt;You get in the business of supporting infrastructure.&lt;/p&gt;

&lt;p&gt;At this point, the problem is no longer writing a &lt;strong&gt;compiler&lt;/strong&gt;. The problem is maintaining &lt;strong&gt;an ecosystem of compilers&lt;/strong&gt; &lt;br&gt;
that all need the same optimizations, the same bug fixes, and the same engineering improvements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it possible to have a compiler infrastructure that supports dozens of programming languages and dozens of processor architectures? &lt;br&gt;
How will it tame such complexity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we examine any data structure, optimization pass, or backend interface, it makes sense to understand the engineering&lt;br&gt;
problem that forced LLVM into existence. &lt;br&gt;
Modern LLVM is best understood as a response to a scaling problem that&lt;br&gt;
traditional compiler architectures could not solve.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The Limitation
&lt;/h2&gt;

&lt;p&gt;A traditional compiler is often presented as a simple linear pipeline.&lt;/p&gt;

&lt;p&gt;For a compiler supporting one language and one architecture, this design works remarkably well. Every phase&lt;br&gt;
is built specifically for that single combination, allowing the implementation to be tightly integrated.&lt;br&gt;
The difficulties begin when either side of the pipeline grows.&lt;br&gt;
Suppose we wish to support five programming languages targeting three processor architectures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Languages                  
|---C                         Targets
|---C++                        
|---Rust               x86       ARM      RISC-V
|---Swift                |________|_________|
|---Zig              

5 Languages × 3 Architectures = 15 compiler combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a shared infrastructure, every combination requires its own compilation pipeline.&lt;br&gt;
If more languages and architectures are added, the amount of duplicated engineering effort &lt;br&gt;
grows rapidly.&lt;/p&gt;

&lt;p&gt;This phenomenon is commonly known as the &lt;strong&gt;N × M problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The difficulty extends far beyond parsing source code or generating machine instructions. &lt;br&gt;
Every compiler benefits from sophisticated analyses and optimizations: dead code elimination,&lt;br&gt;
loop optimizations, common subexpression elimination, function inlining, register allocation,&lt;br&gt;
instruction scheduling, and many others.&lt;/p&gt;

&lt;p&gt;In a traditional architecture, improvements to these algorithms often had to be implemented repeatedly &lt;br&gt;
across multiple compiler implementations. The same optimization might exist in several slightly different&lt;br&gt;
forms, each evolving independently, each accumulating its own bugs and maintenance costs.&lt;/p&gt;

&lt;p&gt;The traditional architecture therefore suffered from two fundamental scaling problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The N × M problem&lt;/strong&gt; — every new frontend or backend increases the number of compiler combinations that must be maintained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A non-reusable middle&lt;/strong&gt; — the most sophisticated and valuable parts of a compiler, namely its analyses and optimizations, could not easily be shared across languages or architectures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As software development expanded beyond a handful of languages and processor families,&lt;br&gt;
these problems became increasingly expensive. The challenge was no longer writing compilers&lt;br&gt;
— it was preventing compiler engineering effort from growing exponentially.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The New Abstraction
&lt;/h2&gt;

&lt;p&gt;While LLVM IR is a critical component of the infrastructure, I would argue that&lt;br&gt;
LLVM's most significant innovation was &lt;strong&gt;not&lt;/strong&gt; the IR&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Its most significant innovation was architectural.&lt;/p&gt;

&lt;p&gt;Rather than treating a compiler as a single executable that performs every stage internally, LLVM treats a compiler as &lt;strong&gt;a collection of reusable libraries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The resulting architecture appears deceptively simple:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fexdx6zp2noqf8cckzhpq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fexdx6zp2noqf8cckzhpq.png" alt="Compiler Pipeline" width="276" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The frontend becomes responsible for understanding a programming language and translating it into LLVM IR.&lt;br&gt;
The middle-end performs analyses and optimizations without needing to know which language produced the IR.&lt;br&gt;
The backend translates that optimized IR into machine code for a specific processor architecture.&lt;/p&gt;

&lt;p&gt;A new language no longer needs to build its own optimizer.&lt;br&gt;
A new processor architecture no longer needs to implement an entire compiler.&lt;br&gt;
Instead, both become clients of a common infrastructure.&lt;/p&gt;

&lt;p&gt;It is important to understand what LLVM &lt;strong&gt;does not&lt;/strong&gt; solve.&lt;/p&gt;

&lt;p&gt;LLVM does not eliminate the complexity of supporting multiple languages or multiple architectures.&lt;br&gt;
A frontend must still understand the syntax and semantics of its source language, and every backend&lt;br&gt;
must still understand the instruction set, calling conventions, and ABI of its target processor.&lt;/p&gt;

&lt;p&gt;What LLVM eliminates is the repeated implementation of everything that lies between those two ends.&lt;/p&gt;

&lt;p&gt;The optimizer, analyses, large portions of the code-generation pipeline, and numerous supporting libraries become shared infrastructure rather than duplicated engineering effort.&lt;/p&gt;

&lt;p&gt;This is the first and most fundamental abstraction in LLVM:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The compiler is no longer a monolithic program. It is a collection of reusable components connected through carefully designed interfaces.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. How It Works
&lt;/h2&gt;

&lt;p&gt;LLVM's architecture is reflected directly in the structure of the project itself.&lt;/p&gt;

&lt;p&gt;At the top level of the repository are independent components rather than a single "compiler" directory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llvm/&lt;/code&gt; — the reusable core libraries, including LLVM IR, analyses, transformations, code generation, target descriptions,&lt;/li&gt;
&lt;li&gt; the MC layer, and TableGen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clang/&lt;/code&gt; — one frontend among many.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lld/&lt;/code&gt; — the linker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compiler-rt/&lt;/code&gt; — runtime support libraries required by generated programs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libcxx/&lt;/code&gt;, &lt;code&gt;libcxxabi/&lt;/code&gt;, and related projects — the C++ standard library implementation and supporting runtime components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This organization reflects LLVM's philosophy.&lt;br&gt;
The optimizer is not owned by Clang.&lt;br&gt;
The backend is not owned by Clang.&lt;br&gt;
The IR is not owned by Clang.&lt;br&gt;
Instead, Clang is itself a client of LLVM's libraries, just as a JIT compiler, a static compiler, a binary translator, &lt;br&gt;
or a research tool can be.&lt;/p&gt;

&lt;p&gt;The pipeline shown earlier therefore represents more than compilation stages.&lt;/p&gt;

&lt;p&gt;Each boundary represents a &lt;strong&gt;library interface&lt;/strong&gt; designed to isolate one class of variation from another.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Why These Boundaries Exist
&lt;/h2&gt;

&lt;p&gt;A recurring theme throughout this series is that LLVM introduces a new abstraction only when an existing one can no longer contain complexity.&lt;/p&gt;

&lt;p&gt;Programming languages evolve independently from processor architectures.&lt;/p&gt;

&lt;p&gt;A language designer may introduce new type systems, ownership models, generics, or concurrency features without changing anything about modern CPUs.&lt;/p&gt;

&lt;p&gt;Likewise, processor architects continue to develop wider vector units, new instruction sets, and increasingly sophisticated microarchitectures without any knowledge of Rust, Swift, or C++.&lt;/p&gt;

&lt;p&gt;LLVM deliberately separates these two axes of variation.&lt;/p&gt;

&lt;p&gt;The boundary between the frontend and the optimizer isolates language-specific complexity.&lt;/p&gt;

&lt;p&gt;The boundary between the optimizer and the backend isolates hardware-specific complexity.&lt;/p&gt;

&lt;p&gt;Rather than allowing these differences to permeate the entire compiler, LLVM confines each category of variation behind a dedicated interface.&lt;/p&gt;

&lt;p&gt;Every major abstraction introduced later in this series follows exactly the same design philosophy.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Design Trade-offs
&lt;/h2&gt;

&lt;p&gt;Library boundaries are not free.&lt;/p&gt;

&lt;p&gt;Every reusable interface must be expressive enough to satisfy many clients without becoming so general that meaningful optimization becomes impossible.&lt;/p&gt;

&lt;p&gt;LLVM IR must simultaneously support Clang, Rust, Swift, Julia, Zig, and many other frontends while remaining suitable for aggressive optimization.&lt;/p&gt;

&lt;p&gt;Likewise, backend interfaces must describe processors ranging from small embedded microcontrollers to large server CPUs and GPUs.&lt;/p&gt;

&lt;p&gt;Designing these interfaces therefore becomes an exercise in balance.&lt;/p&gt;

&lt;p&gt;Interfaces that are too narrow cannot accommodate new languages or architectures.&lt;/p&gt;

&lt;p&gt;Interfaces that are too broad lose precision and become increasingly difficult to optimize effectively.&lt;/p&gt;

&lt;p&gt;Much of LLVM's evolution can be understood as the continuous refinement of these interfaces.&lt;/p&gt;

&lt;p&gt;The project deliberately accepts additional compile-time complexity and larger libraries in exchange for dramatically lower long-term engineering cost and substantially greater code reuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The Abstraction Leaks
&lt;/h2&gt;

&lt;p&gt;Even at this highest level the abstraction is imperfect.&lt;/p&gt;

&lt;p&gt;Target independence is an objective rather than an absolute guarantee.&lt;/p&gt;

&lt;p&gt;Certain information about the target machine must already be available while LLVM IR is being generated. Calling conventions, data layouts, pointer sizes, address spaces, and other architectural properties influence the correctness of generated programs.&lt;/p&gt;

&lt;p&gt;Consequently, LLVM provides carefully controlled mechanisms through which target-specific knowledge flows into otherwise target-independent components.&lt;/p&gt;

&lt;p&gt;These leaks are not failures of the design.&lt;/p&gt;

&lt;p&gt;They are examples of a recurring engineering principle that appears throughout LLVM:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good abstractions isolate complexity; they do not pretend that complexity no longer exists.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  8. Source Tour
&lt;/h2&gt;

&lt;p&gt;The top-level structure of the LLVM project already reveals its architectural philosophy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llvm/&lt;/code&gt; — reusable compiler infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clang/&lt;/code&gt; — one frontend built on that infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lld/&lt;/code&gt; — the linker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compiler-rt/&lt;/code&gt; — runtime libraries.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libcxx/&lt;/code&gt; and related projects — standard library components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what is absent.&lt;/p&gt;

&lt;p&gt;There is no directory that simply contains "the compiler."&lt;/p&gt;

&lt;p&gt;Instead, there is a collection of interoperable components that can be assembled in different ways depending on the needs of the tool being built.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Looking Ahead
&lt;/h2&gt;

&lt;p&gt;We now understand &lt;strong&gt;why&lt;/strong&gt; LLVM was organized as a collection of reusable libraries.&lt;/p&gt;

&lt;p&gt;That naturally leads to a deeper question.&lt;/p&gt;

&lt;p&gt;If dozens of programming languages are expected to share a single optimizer, &lt;strong&gt;what common language do they all speak?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;LLVM IR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Far more than an intermediate representation, LLVM IR is the central contract around which the entire LLVM ecosystem is built. It is expressive enough to represent a wide variety of source languages, yet constrained enough to enable powerful, target-independent optimizations.&lt;/p&gt;

&lt;p&gt;In Part 2, we will examine why abstract syntax trees are insufficient, why raw assembly is insufficient, and how LLVM IR was engineered to occupy the narrow but crucial space between them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Principle #1 — Isolate variations
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When different parts of a system evolve for different reasons, separate them behind a stable interface.&lt;/strong&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Programming languages and processor architectures evolve independently. LLVM's first architectural decision was to isolate those two axes of change so that new languages and new targets could reuse the same optimization and code-generation infrastructure. Every major abstraction introduced later in LLVM is an extension of this same principle.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Series Thread&lt;/strong&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every abstraction in LLVM exists because the previous one could not adequately contain a particular form of complexity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The traditional monolithic compiler could not solve the &lt;strong&gt;N × M scaling problem&lt;/strong&gt;. LLVM's first abstraction—treating the compiler as a collection of reusable libraries—was introduced to solve exactly that problem. Every subsequent layer in the series will reveal another engineering challenge, another abstraction, and another carefully chosen boundary.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Joran Dirk Greef &lt;a href="https://www.youtube.com/watch?v=yKgfk8lTQuE&amp;amp;t=2793s&amp;amp;pp=ygUZdGhlIHBvd2VyIG9mIGFuIGludGVyZmFjZQ%3D%3D" rel="noopener noreferrer"&gt;may disagree with me on this&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;One thing that is observable in hardware/sotware interface, any time one encounters a relatively radical idea, chances are a new functional unit is involved.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
    </item>
  </channel>
</rss>
