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

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 and 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. 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 uses Mem2RegInfo to track how many allocas were eliminated, while the Dead Code Elimination pass in src/xir/passes/dce.cpp populates DCEInfo with counts of removed instructions.

The system leverages several core infrastructure components:

  • XIRBuilder (src/xir/builder.h): Creates new instructions, basic blocks, and phi nodes during transformation.
  • DomTree (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 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:

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 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 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:

// 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 for instruction removal and 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 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 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 and 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, 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →