# Understanding the LuisaCompute IR Pass System: Implementing Custom DCE and Mem2Reg Transformations

> Explore the LuisaCompute IR pass system for XIR transformations. Learn to implement custom DCE and Mem2Reg optimizations using XIRBuilder and DomTree for efficient code enhancement.

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

---

**The LuisaCompute IR pass system provides a modular framework for transforming XIR (eXtended Intermediate Representation) through self-contained optimization passes located in `src/xir/passes/`, enabling developers to implement custom transformations like Dead Code Elimination (DCE) and Memory-to-Register (Mem2Reg) promotion using standard infrastructure such as `XIRBuilder` and `DomTree`.**

LuisaCompute represents GPU shaders and compute kernels in XIR, a self-hosted intermediate representation designed for high-performance code generation. The IR pass system organizes transformations as discrete algorithms that analyze and rewrite this representation, with core implementations like [`mem2reg.cpp`](https://github.com/luisagroup/luisacompute/blob/main/mem2reg.cpp) and [`dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/dce.cpp) demonstrating the canonical patterns for modifying control flow, eliminating dead instructions, and promoting stack allocations to registers.

## What is the IR Pass System?

The IR pass system in LuisaCompute is the compilation framework responsible for optimizing and lowering XIR before code generation. Passes are self-contained transformation algorithms that traverse the instruction-level IR and apply specific optimizations.

All passes reside under `src/xir/passes/` and operate on `Function` objects defined in [`src/xir/module.h`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/module.h). Each pass typically accepts a function pointer and an info struct that collects statistics about the transformation. For example, the Memory-to-Register promotion pass in [`src/xir/passes/mem2reg.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/mem2reg.cpp) uses `Mem2RegInfo` to track how many allocas were eliminated, while the Dead Code Elimination pass in [`src/xir/passes/dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dce.cpp) populates `DCEInfo` with counts of removed instructions.

The system leverages several core infrastructure components:

- **`XIRBuilder`** ([`src/xir/builder.h`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/builder.h)): Creates new instructions, basic blocks, and phi nodes during transformation.
- **`DomTree`** ([`src/xir/passes/dom_tree.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dom_tree.cpp)): Computes dominance relationships and frontiers required for SSA construction.
- **Pass contexts**: Structures like `Mem2RegPassContext` manage temporary state and lifetime holders for removed instructions.

## Core Infrastructure for Custom Passes

Before implementing a custom transformation, understand these fundamental building blocks used across the codebase.

### XIRBuilder and Instruction API

The `XIRBuilder` class provides a fluent interface for constructing IR elements. When transforming code, passes use builders to insert phi nodes, arithmetic operations, or control flow instructions. The builder maintains an insertion point within a basic block, allowing passes to emit new instructions at specific locations.

### Dominance Analysis

Many advanced passes require dominance information to place phi nodes correctly or determine variable liveness. The `DomTree` class in [`src/xir/passes/dom_tree.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dom_tree.cpp) computes the dominance tree and dominance frontiers for a function's control flow graph. Passes like Mem2Reg rely on this analysis to identify where to insert phi nodes when promoting allocas to registers.

### Pass Info Structs

Each pass defines a lightweight info struct to return statistics. For instance:

```cpp
struct Mem2RegInfo {
    uint promoted_allocas = 0;
    uint inserted_phis = 0;
};

struct DCEInfo {
    uint removed_instructions = 0;
};

```

These structs enable the caller to log optimization effectiveness and debug pass behavior.

## How to Implement a Custom Transformation

Follow this step-by-step process to create a new IR pass, using the elimination of redundant `add 0` operations as a concrete example.

1. **Create the implementation file**: Add [`src/xir/passes/my_pass.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/my_pass.cpp) to the passes directory. The CMake build system in `src/xir` automatically globbs all `.cpp` files, so no manual build file editing is required.

2. **Define an info struct**: Create a struct to track transformation statistics.

3. **Implement the traversal logic**: Use `function->definition()->traverse_instructions()` to visit every instruction in the function.

4. **Apply transformations**: Use `replace_all_uses_with()` to redirect values and `remove_self()` to delete dead instructions.

5. **Expose the entry function**: Implement `void run_my_pass(Function *f, MyPassInfo &info)` as the public API.

6. **Register the pass**: Add a call to your entry function in the pass dispatcher (typically [`src/xir/passes/passes.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/passes.cpp) or the module processing logic).

### Minimal Example: Eliminating Redundant Additions

The following implementation demonstrates a complete custom pass that removes arithmetic instructions adding zero to a value:

```cpp
// src/xir/passes/my_pass.cpp
#include <luisa/xir/passes/helpers.h>
#include <luisa/xir/builder.h>
#include <luisa/xir/module.h>

namespace luisa::compute::xir {

struct MyPassInfo {
    uint removed_inst = 0;
};

void run_my_pass(Function *function, MyPassInfo &info) noexcept {
    if (auto def = function->definition()) {
        def->traverse_instructions([&](Instruction *inst) noexcept {
            if (inst->derived_instruction_tag() == DerivedInstructionTag::ARITHMETIC) {
                auto arith = static_cast<ArithmeticInst *>(inst);
                if (arith->op() == ArithmeticOp::ADD) {
                    Value *lhs = arith->lhs();
                    Value *rhs = arith->rhs();
                    auto is_zero = [](Value *v) {
                        return v->isa<Constant>() && static_cast<Constant *>(v)->as<int>() == 0;
                    };
                    if (is_zero(lhs) || is_zero(rhs)) {
                        Value *keep = is_zero(lhs) ? rhs : lhs;
                        arith->replace_all_uses_with(keep);
                        arith->remove_self();
                        ++info.removed_inst;
                    }
                }
            }
        });
    }
}

} // namespace luisa::compute::xir

```

This pattern mirrors the style used in [`dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/dce.cpp) for instruction removal and [`mem2reg.cpp`](https://github.com/luisagroup/luisacompute/blob/main/mem2reg.cpp) for use replacement.

## Reference Implementations: DCE and Mem2Reg

Studying the existing passes provides concrete templates for specific transformation categories.

### Dead Code Elimination (DCE)

The DCE pass in [`src/xir/passes/dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dce.cpp) implements a classic mark-and-sweep algorithm for XIR. It traverses instructions to identify those with no side effects and no uses, then removes them from the function. The pass populates `DCEInfo` to report how many instructions were eliminated, providing a template for any removal-based transformation.

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

[`src/xir/passes/mem2reg.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/mem2reg.cpp) implements the well-known Mem2Reg optimization that promotes stack allocations (allocas) to SSA registers. This pass demonstrates:

- Using `DomTree` to compute dominance frontiers for phi placement.
- Iterating over alloca instructions and their uses.
- Constructing phi nodes via `XIRBuilder` at merge points.
- Handling `Mem2RegPassContext` for temporary lifetime management.

The entry function `run_mem2reg_pass(Function *f, Mem2RegInfo &info)` serves as the canonical example of a complex analysis-driven transformation.

## Summary

- **The LuisaCompute IR pass system** organizes XIR transformations as modular passes in `src/xir/passes/`, utilizing `XIRBuilder` and `DomTree` for construction and analysis.
- **Custom passes** require an info struct, a traversal using `traverse_instructions()`, and cleanup via `replace_all_uses_with()` and `remove_self()`.
- **Reference implementations** in [`mem2reg.cpp`](https://github.com/luisagroup/luisacompute/blob/main/mem2reg.cpp) and [`dce.cpp`](https://github.com/luisagroup/luisacompute/blob/main/dce.cpp) demonstrate complex optimization patterns including SSA construction and dead instruction removal.
- **Registration** occurs in the passes dispatcher, with automatic CMake integration for new files in the passes directory.

## Frequently Asked Questions

### What distinguishes the Mem2Reg pass from Dead Code Elimination?

Mem2Reg transforms memory operations into register values by inserting phi nodes and eliminating allocas, requiring dominance analysis from [`src/xir/passes/dom_tree.cpp`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/dom_tree.cpp), while DCE simply removes instructions with no side effects and no uses without inserting new IR elements.

### How do I access dominance information in a custom pass?

Include the dominance analysis headers and construct or access the `DomTree` for your target function; the tree provides `dominates()` methods and frontier iterators necessary for placing phi nodes during SSA construction.

### Where should I place utility functions shared across multiple passes?

Place reusable helper functions in [`src/xir/passes/helpers.h`](https://github.com/luisagroup/luisacompute/blob/main/src/xir/passes/helpers.h), following the pattern of existing utilities like `remove_all_uses` and `mark_as_removed` that provide common instruction manipulation primitives.

### How do I verify that my custom pass preserves program correctness?

Run the existing test suite via `ctest` after registering your pass, and add targeted unit tests that create minimal XIR functions exercising your transformation's boundary conditions, checking that the `Info` struct reports expected modification counts.