Major Updates in LLVM

  • 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

Updated: