LLVM has been under active development with continuous version updates, and its IR has seen both major and minor changes over time.
Let’s go over some of the notable changes in recent versions, and how they differ from older ones:
Typed Pointer -> Opaque Pointer Transition
Starting from LLVM 15, the pointer type system in LLVM IR underwent a major change.
In the past, pointer types carried the type of what they pointed to, e.g. i8*, i32*, %MyType*.
From LLVM 15 onward, all pointers are unified into a single ptr type.
For example, previously i8* and i32* were distinct types, but now both are simply represented as ptr.
This is called the Opaque Pointer model, simplifying IR by removing type information from pointers.
As a result, when looking at old IR with a modern compiler, pointer-related syntax may look different (e.g., getelementptr no longer shows the pointee type explicitly).
While this change impacts LLVM API users as well, conceptually you can now think of pointers simply as untyped addresses.
Unifying Function Memory Access Attributes
In LLVM 16, several attributes describing a function’s memory behavior (readnone, readonly, writeonly, argmemonly, inaccessiblememonly, etc.) were consolidated into a single memory(...) attribute.
For instance, what used to be readonly is now expressed as memory(read), and readnone is expressed as memory(none).
This change makes IR metadata more consistent, but can cause incompatibilities with older IR.
When you see memory(...) in modern IR function signatures, it’s replacing those legacy attributes.
Changes in Debug Information Representation
Up through LLVM 14, debug info in IR appeared as intrinsic calls like llvm.dbg.declare / llvm.dbg.value combined with metadata nodes like !DILocation.
In newer versions, debug info is no longer represented as fake instructions but rather as dedicated records.
As a result, modern IR outputs may not show dbg.declare calls anymore; instead, debug metadata is attached separately (sometimes shown as #dbg annotations in assembly form).
The goal is to avoid confusing optimization passes with non-semantic debug intrinsics.
For tool developers, this is an important difference, while for regular users it just means that debug intrinsics are gone in newer IR.
Pass Manager Replacement
Between LLVM 8–11, the old Legacy Pass Manager was fully replaced by the New Pass Manager.
Many older resources still reference llvm::PassManager, but modern code now uses llvm::FunctionPassManager and llvm::ModulePassManager.
Other IR Syntax and Feature Improvements
Various other refinements have occurred over time:
In LLVM 15, intrinsics like llvm.experimental.vector.extract / insert were renamed to llvm.vector.extract / insert (dropping the “experimental” tag).
In LLVM 16, constant-expression forms of some instructions (like fneg) were removed, requiring them to be emitted as instructions instead.
Atomic memory operations gained support for fmax and fmin.
The special callbr instruction syntax was simplified.
Platform and Subproject Updates
LLVM as a project has also expanded:
Around LLVM 15, the RISC-V backend became production-ready.
Around LLVM 20 (2024–2025), Flang (Fortran frontend) was finally included in official releases after long development.
The introduction of MLIR added a higher-level IR framework for domain-specific optimizations, especially useful in AI/ML compilers and DSL compilers.
These changes don’t alter LLVM IR itself directly but demonstrate the growing scope