# What is XIR and How Does It Enable Advanced Compiler Optimizations in LuisaCompute?

> Discover XIR, LuisaCompute's SSA-based intermediate representation enabling advanced platform-agnostic compiler optimizations like dead-code elimination and automatic differentiation before code generation.

- Repository: [LuisaGroup/luisacompute](https://github.com/luisagroup/luisacompute)
- Tags: deep-dive
- Published: 2026-03-06

---

**XIR (Luisa eXperimental Intermediate Representation) is an SSA-based intermediate representation introduced in LuisaCompute v2 that sits between the high-level C++ DSL and backend code generators, enabling platform-agnostic optimizations like dead-code elimination, register promotion, and automatic differentiation before target-specific code emission.**

XIR serves as the central optimization layer in the [luisagroup/luisacompute](https://github.com/luisagroup/luisacompute) rendering framework. By translating the abstract syntax tree (AST) generated by the C++ DSL into a uniform intermediate representation, the compiler can apply advanced transformations once and benefit all backends—including CUDA, DirectX, Metal, and CPU—without duplicating optimization logic.

## Understanding XIR Architecture

XIR is built around a hierarchical set of C++ classes that model program structure in **Static Single Assignment (SSA)** form. The core components are defined in the `include/luisa/xir/` directory.

### Core IR Types

The foundation of XIR resides in [[`include/luisa/xir/value.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/value.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/value.h), which defines the `Value` class. In SSA form, every `Value` is defined exactly once, making data-flow analysis trivial. The file also declares `Use` and `Metadata` classes that track dependencies and annotations.

Modules serve as containers for entire programs. [[`include/luisa/xir/module.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/module.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/module.h) defines the `Module` class, which holds collections of functions, global variables, and module-level metadata.

### Translation from AST

The bridge between the high-level DSL and XIR is implemented in [[`include/luisa/xir/translators/ast2xir.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/ast2xir.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/translators/ast2xir.h). This header exposes the translation API with three key functions:

- `ast_to_xir_translate_begin()` – initializes a translation context with an `AST2XIRConfig`
- `ast_to_xir_translate_add_function()` – converts individual AST functions into XIR
- `ast_to_xir_translate_finalize()` – produces a complete `XIR::Module` ready for optimization

## How XIR Enables Advanced Compiler Optimizations

XIR unlocks sophisticated compiler optimizations through three architectural decisions: strict SSA form, an explicit Control-Flow Graph (CFG), and a modular pass infrastructure.

### SSA Form Simplifies Data-Flow Analysis

Because every value in XIR is defined exactly once, optimizations like **constant propagation** and **dead-code elimination** require only local analysis. The compiler can traverse the use-def chains embedded in the `Value` and `Use` objects without building complex reaching-definitions tables.

### Explicit CFG Enables Control-Flow Optimizations

XIR functions contain explicit `BasicBlock` objects linked by terminator instructions (`If`, `Switch`, `Unreachable`). This structure allows passes to compute **dominance trees**, identify **unreachable blocks**, and perform **loop simplification**—transformations that are impossible when targeting backend languages directly.

### Modular Pass Infrastructure

The pass system defined in [[`include/luisa/xir/passes/helpers.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/helpers.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/helpers.h) provides utilities like `XIRBuilder` and clone helpers. Individual optimizations inherit from a generic pass base, ensuring consistent interfaces. This design mirrors the "opt-pipeline" approach used in LLVM, allowing passes to be composed, repeated, and tested independently.

## The XIR Optimization Pipeline

LuisaCompute ships with a rich library of optimization passes that operate directly on XIR, benefiting all backends simultaneously.

### Dead-Code Elimination (DCE)

The DCE pass removes instructions whose results are never used and eliminates unreachable basic blocks. The API is declared in [[`include/luisa/xir/passes/dce.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/dce.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/dce.h), with the implementation in [[`src/xir/passes/dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dce.cpp)](https://github.com/luisagroup/luisacompute/blob/stable/src/xir/passes/dce.cpp).

The function `run_dce_pass_on_function()` repeatedly applies propagation of unreachable blocks, CFG cleanup, phi-node reduction, and dead-allocation elimination until a fixed point is reached.

### Memory-to-Register Promotion (Mem2Reg)

The **Mem2Reg** pass promotes stack allocations (`alloca`) to SSA registers, reducing memory traffic. This is critical for GPU performance, where register access is orders of magnitude faster than local memory. The API is defined in [[`include/luisa/xir/passes/mem2reg.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/mem2reg.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/mem2reg.h).

### Automatic Differentiation (Autodiff)

XIR enables automatic differentiation by inserting gradient-tracking instructions directly into the IR. The `autodiff_pass_run_on_function()` transform, declared in [[`include/luisa/xir/passes/autodiff.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/autodiff.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/autodiff.h), operates on the SSA form to generate adjoint code without modifying the high-level DSL.

### Additional Passes

The repository includes passes for **local store forwarding**, **early return elimination**, and **loop simplification**, all located under `include/luisa/xir/passes/`. Because these operate on the target-independent XIR, improvements to these passes automatically benefit CUDA, DirectX, Metal, and CPU backends.

## Working with XIR in Practice

### Enabling XIR in Your Build

XIR is an experimental feature controlled by the `lc_enable_xir` option in the build system. To enable it using xmake:

```bash
xmake f --lc_enable_xir=true
xmake

```

This option is defined in the root [[`xmake.lua`](https://github.com/luisagroup/luisacompute/blob/main/xmake.lua)](https://github.com/luisagroup/luisacompute/blob/stable/xmake.lua) around line 5000.

### Translating AST to XIR

The C++ API for converting high-level DSL functions into XIR is straightforward:

```cpp
#include <luisa/xir/translators/ast2xir.h>
#include <luisa/xir/translators/xir2text.h>

using namespace luisa::compute::xir;

int main() {
    // Configure translation
    AST2XIRConfig cfg{};
    
    // Initialize context
    auto *ctx = ast_to_xir_translate_begin(cfg);
    
    // Add kernel functions (my_kernel is an ASTFunction)
    ast_to_xir_translate_add_function(ctx, my_kernel);
    
    // Finalize to get XIR module
    auto module = ast_to_xir_translate_finalize(ctx);
    
    // Now ready for optimization passes
}

```

Key functions `ast_to_xir_translate_begin`, `ast_to_xir_translate_add_function`, and `ast_to_xir_translate_finalize` are declared in [[`include/luisa/xir/translators/ast2xir.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/ast2xir.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/translators/ast2xir.h).

### Running Optimization Passes

After translation, you can run specific optimization passes manually:

```cpp
#include <luisa/xir/passes/mem2reg.h>
#include <luisa/xir/passes/dce.h>

// Promote stack allocations to registers
Mem2RegInfo mem2reg_info = mem2reg_pass_run_on_module(module.get());
std::cout << "Promoted " << mem2reg_info.promoted_allocas << " allocas\n";

// Remove dead code
DCEInfo dce_info = dce_pass_run_on_module(module.get());

```

The Mem2Reg API is defined in [[`include/luisa/xir/passes/mem2reg.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/mem2reg.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/mem2reg.h), while DCE is declared in [[`include/luisa/xir/passes/dce.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/dce.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/passes/dce.h) and implemented in [[`src/xir/passes/dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dce.cpp)](https://github.com/luisagroup/luisacompute/blob/stable/src/xir/passes/dce.cpp).

### Debugging with XIR Dumps

XIR provides human-readable and machine-readable serialization for debugging:

```cpp
#include <luisa/xir/translators/xir2text.h>
#include <luisa/xir/translators/xir2json.h>

// Human-readable text dump
std::string text = xir_to_text_translate(module.get(), /*debug_info=*/true);
std::cout << text << std::endl;

// JSON for tooling
std::string json = xir_to_json_translate(module.get());
std::ofstream out("kernel.xir.json");
out << json;

```

The text translator is declared in [[`include/luisa/xir/translators/xir2text.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/xir2text.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/translators/xir2text.h), and the JSON translator in [[`include/luisa/xir/translators/xir2json.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/xir2json.h)](https://github.com/luisagroup/luisacompute/blob/stable/include/luisa/xir/translators/xir2json.h).

## Summary

- **XIR** is the SSA-based intermediate representation in LuisaCompute v2 that bridges the high-level C++ DSL and backend code generators.
- By enforcing **Static Single Assignment** form, XIR makes data-flow analysis trivial, enabling optimizations like constant propagation and dead-code elimination.
- The **explicit Control-Flow Graph** (CFG) with `BasicBlock` and terminator instructions allows passes to compute dominance trees and perform control-flow optimizations.
- XIR provides a **modular pass infrastructure** including Dead-Code Elimination (`dce_pass_run_on_module`), Mem2Reg promotion (`mem2reg_pass_run_on_module`), and Automatic Differentiation (`autodiff_pass_run_on_function`).
- Because optimizations run on XIR before backend code generation, improvements automatically benefit all targets: CUDA, DirectX, Metal, and CPU.
- Developers can inspect XIR using `xir_to_text_translate` and `xir_to_json_translate` for debugging and verification.

## Frequently Asked Questions

### What does XIR stand for in LuisaCompute?

XIR stands for **Luisa eXperimental Intermediate Representation**. It is the SSA-based IR introduced in LuisaCompute v2 to replace direct AST-to-backend translation, providing a centralized layer for advanced compiler optimizations.

### How does XIR differ from the previous compilation pipeline?

Previously, LuisaCompute compiled the high-level DSL AST directly to backend-specific code. XIR introduces an intermediate step where the AST is first translated into a platform-agnostic SSA form (`ast_to_xir_translate_begin`, `ast_to_xir_translate_finalize`). This allows optimizations to be written once in `src/xir/passes/` and applied universally before CUDA, DirectX, Metal, or CPU code generation.

### What optimization passes are available for XIR?

LuisaCompute ships with a rich pass library including **Dead-Code Elimination** ([`dce.h`](https://github.com/luisagroup/luisacompute/blob/main/dce.h)/[`dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/dce.cpp)), **Mem2Reg** promotion ([`mem2reg.h`](https://github.com/luisagroup/luisacompute/blob/main/mem2reg.h)), **Local Store Forwarding**, **Automatic Differentiation** ([`autodiff.h`](https://github.com/luisagroup/luisacompute/blob/main/autodiff.h)), **Early Return Elimination**, and **Loop Simplification**. These passes operate on the explicit CFG and SSA structure to reduce memory traffic, remove redundant computations, and inject gradient-tracking code.

### How can I debug XIR during development?

You can serialize XIR modules to human-readable text using `xir_to_text_translate` (declared in [`include/luisa/xir/translators/xir2text.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/xir2text.h)) or to JSON using `xir_to_json_translate` (from [`include/luisa/xir/translators/xir2json.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/xir2json.h)). These dumps include debug information and can be written to files for inspection, allowing you to verify that optimizations like Mem2Reg or DCE have correctly transformed the code before backend compilation.