Inferensys

Glossary

Intermediate Representation (IR)

An Intermediate Representation (IR) is a compiler's internal, architecture-agnostic data structure or code used to represent a program, enabling machine-independent analysis and transformations.
Data scientist building training data pipeline on laptop, data preprocessing visible, technical workspace.
COMPILER OPTIMIZATION

What is Intermediate Representation (IR)?

An Intermediate Representation is the core data structure within a compiler that enables machine-independent program analysis and transformation.

An Intermediate Representation (IR) is a compiler's internal, architecture-agnostic data structure or code used to represent a program, enabling machine-independent analysis and transformations before being lowered to target-specific machine code. It serves as a crucial abstraction layer, decoupling the front-end (parsing source code) from the back-end (generating optimized assembly). Common forms include abstract syntax trees (ASTs), control flow graphs (CFGs), and Static Single Assignment (SSA) form, each facilitating specific optimizations like dead code elimination and constant propagation.

In the context of Neural Processing Unit (NPU) acceleration, the IR is the central artifact for kernel fusion and optimization. Compilers like LLVM or MLIR use sophisticated IRs to represent computational graphs, allowing transformations such as loop tiling, memory coalescing, and operation fusion to be applied generically before mapping to specific NPU instructions. This hardware-agnostic optimization phase is essential for maximizing arithmetic intensity and minimizing data movement across the memory hierarchy.

COMPILER INTERNALS

Key Characteristics of an IR

An Intermediate Representation (IR) is the compiler's internal data structure that enables machine-independent analysis and transformation. Its design dictates the efficacy of all subsequent optimization passes.

01

Abstraction Level

An IR sits between the high-level source code and low-level machine code. Its abstraction level is a primary design choice. High-Level IRs (HIR) retain rich source-level semantics (e.g., types, control structures), while Low-Level IRs (LIR) resemble assembly with virtual registers and explicit data flow. The choice determines which optimizations are feasible; HIRs enable complex loop transformations, while LIRs facilitate precise register allocation and instruction scheduling.

02

Graph vs. Linear Form

IRs are structurally categorized by their representation. Graph-based IRs, like LLVM's SSA form or a Control Flow Graph (CFG), explicitly model data dependencies and program flow as nodes and edges, enabling powerful global optimizations. Linear IRs, resembling pseudo-assembly lists, are simpler and faster to process for local transformations. Modern compilers often use multiple forms, lowering from graph to linear representations in later stages.

03

Single Static Assignment (SSA) Form

Single Static Assignment is a pivotal property of many modern IRs where each variable is assigned exactly once. It simplifies data-flow analysis by making def-use chains explicit. Phi (φ) functions are inserted at control flow merge points to select the correct variable version. This form enables powerful optimizations like:

  • Constant Propagation
  • Dead Code Elimination
  • Global Value Numbering LLVM IR and GCC's GIMPLE are prominent SSA-based IRs.
04

Target Independence

A core purpose of an IR is to be architecture-agnostic. It abstracts away hardware specifics like register counts, instruction sets, and memory addressing modes. This allows a frontend (e.g., for C++, Rust) to generate the same IR, and a backend to lower it to various targets (x86, ARM, NPU). The IR defines a set of virtual instructions and abstract resources that backends map to concrete hardware, separating machine-independent optimizations from target-specific code generation.

05

Optimization Enabler

The IR is the substrate for all compiler optimizations. Its design dictates which transformations are natural and efficient. Common IR-level optimizations include:

  • Loop-Invariant Code Motion: Hoists computations outside loops.
  • Common Subexpression Elimination (CSE): Removes redundant calculations.
  • Instruction Combining: Fuses adjacent operations (e.g., add+load).
  • Inlining: Replaces function calls with the function body. A well-designed IR makes these analyses straightforward and the transformations safe and correct.
06

Multi-Stage Lowering

Sophisticated compilers use a series of IRs in a lowering pipeline. A program may progress from an Abstract Syntax Tree (AST) to a High-Level IR (HIR), then to a Mid-Level IR (MIR) in SSA form for optimizations, and finally to a Low-Level IR (LIR) or Machine IR for register allocation and instruction selection. Each stage reduces abstraction and incorporates more target-specific information, enabling a phased optimization strategy where high-level semantics are gradually refined into executable code.

Kernel Fusion and Optimization

The Role of IR in the Compilation Pipeline

Intermediate Representation (IR) is the compiler's core data structure, enabling machine-independent analysis and transformation before generating target-specific machine code.

An Intermediate Representation (IR) is a compiler's architecture-agnostic data structure or code used to represent a program. It serves as a central, manipulable abstraction between the high-level source code and the low-level target machine code. This separation enables critical machine-independent optimizations—such as common subexpression elimination, dead code elimination, and constant propagation—to be performed once, before the complexities of the final hardware target are introduced. The IR acts as the definitive source of truth for the program's semantics throughout the compilation process.

Within the NPU compilation pipeline, the IR is the substrate for hardware-specific optimizations like kernel fusion and loop nest optimization. The compiler analyzes and transforms the IR graph to merge operations, improve data locality, and schedule computations to maximize parallelism and minimize data movement across the accelerator's memory hierarchy. A well-designed IR allows these sophisticated transformations to be expressed and validated cleanly, directly enabling the generation of highly efficient, fused kernels that fully utilize the NPU's tensor cores and SIMT execution model.

COMPILER INTERNALS

Common Intermediate Representations

An Intermediate Representation (IR) is a compiler's internal, architecture-agnostic data structure used to represent a program, enabling machine-independent analysis and transformations before being lowered to target-specific machine code.

01

Abstract Syntax Tree (AST)

A hierarchical tree representation of the source code's syntactic structure, where each node denotes a construct (e.g., a loop, an assignment). The AST preserves the nesting and order of the original program, making it ideal for high-level semantic analysis and refactoring but often too verbose for low-level optimizations.

  • Key Use: Syntax checking, macro expansion, and initial semantic analysis.
  • Limitation: Contains syntactic sugar and is closely tied to the source language.
02

Three-Address Code (TAC)

A linear, low-level IR where each instruction typically has at most one operator and three operands (e.g., t1 = b + c). It breaks down complex expressions into simple instructions, making data flow and dependencies explicit for optimization passes.

  • Key Characteristics: Uses temporary variables to hold intermediate values.
  • Common Form: Often represented as quadruples (op, arg1, arg2, result).
  • Optimizations Enabled: Common subexpression elimination, constant propagation, and copy propagation work directly on this form.
03

Static Single Assignment (SSA) Form

An IR property where each variable is assigned exactly once, and new versions (denoted by subscripts, e.g., x1, x2) are created at each assignment. Phi (φ) functions are inserted at control flow merge points to select the correct variable version.

  • Primary Benefit: Makes def-use chains explicit and trivial, dramatically simplifying many optimizations.
  • Critical Optimizations: SSA enables sparse conditional constant propagation and global value numbering efficiently.
  • Industry Standard: Used in LLVM IR and the GCC GIMPLE IR.
05

Control Flow Graph (CFG)

A directed graph representation of a program where nodes represent basic blocks (sequences of instructions with a single entry and exit point) and edges represent possible control flow transfers (jumps, branches). It is not a standalone IR but a critical data structure built on top of a linear IR like TAC.

  • Fundamental Purpose: Enables analysis and optimization of program flow, such as dead code elimination, loop detection, and dominance analysis.
  • Core Concept: The foundation for understanding loops, dominance frontiers, and data flow analysis.
06

Sea of Nodes / Program Dependence Graph

An IR that represents a program as a graph where nodes are operations (or values) and edges represent data dependencies and control dependencies. This representation merges the CFG and data flow graph, making implicit parallelism and scheduling constraints explicit.

  • Key Advantage: Allows highly flexible, semantics-preserving code motion and optimization scheduling.
  • Use in Industry: Forms the basis for the Java HotSpot Server Compiler (C2) and Google's V8 TurboFan JIT compiler.
  • Ideal For: Just-in-time compilers and aggressive instruction scheduling.
INTERMEDIATE REPRESENTATION (IR)

Frequently Asked Questions

An Intermediate Representation (IR) is a compiler's internal, architecture-agnostic data structure used to represent a program, enabling machine-independent analysis and transformations before being lowered to target-specific machine code. This FAQ addresses its role in modern AI compiler stacks, particularly for NPU acceleration.

An Intermediate Representation (IR) is a compiler's internal, architecture-agnostic data structure or code used to represent a program between its source form and final machine code. It works by abstracting away source language specifics (like Python or C++) and target hardware details, enabling a sequence of machine-independent optimizations. The compiler first translates source code into an IR (a process called lowering), performs analyses and transformations on this IR, and finally lowers it again to the specific Instruction Set Architecture (ISA) of the target CPU, GPU, or NPU. This separation of concerns is fundamental to supporting multiple front-end languages and back-end hardware targets from a single compiler core.

Prasad Kumkar

About the author

Prasad Kumkar

CEO & MD, Inference Systems

Prasad Kumkar is the CEO & MD of Inference Systems and writes about AI systems architecture, LLM infrastructure, model serving, evaluation, and production deployment. Over 5+ years, he has worked across computer vision models, L5 autonomous vehicle systems, and LLM research, with a focus on taking complex AI ideas into real-world engineering systems.

His work and writing cover AI systems, large language models, AI agents, multimodal systems, autonomous systems, inference optimization, RAG, evaluation, and production AI engineering.