# LuisaCompute IR v2 Architecture: Differences from the Original AST System

> Explore the LuisaCompute IR v2 architecture, an SSA-based representation with an explicit CFG. Discover how it improves upon the original AST system for advanced optimizations and backend code generation.

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

---

**IR v2 (XIR) introduces an SSA-based intermediate representation with an explicit Control-Flow Graph (CFG) that decouples high-level DSL parsing from backend code generation, enabling sophisticated optimization passes that the original AST tree structure could not support.**

LuisaCompute v2 replaces the original Abstract Syntax Tree (AST) system with a dedicated Intermediate Representation called XIR. This architectural shift transforms the compilation pipeline from a simple tree-walking approach into a modern, optimization-focused workflow. Understanding the IR v2 architecture requires examining how it differs from the original AST system in structure, optimization capabilities, and backend integration.

## Core Differences Between IR v2 and the Original AST

### Representation Structure

The original AST system stores programs as trees of `Expression` and `Statement` objects defined in `include/luisa/ast/*`, specifically managed by `FunctionBuilder` in [`function_builder.h`](https://github.com/luisagroup/luisacompute/blob/main/function_builder.h). Variables appear as `Var<T>` objects referencing AST nodes without SSA form. In contrast, IR v2 represents every value as a distinct `Value*` in strict SSA form, with control flow explicitly modeled through `BasicBlock` nodes, `PhiInst` instructions, and branch operations located in [`include/luisa/xir/value.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/value.h) and related headers.

### Optimization Pipeline

Optimization capabilities differ fundamentally between the two systems. The original AST offers limited optimization potential, relying primarily on backends to optimize raw generated code. IR v2 introduces a dedicated pass infrastructure in `include/luisa/xir/passes/`, exposing transformations like `mem2reg` (register promotion), `dce` (dead code elimination), `autodiff`, and `outline`. These passes operate on the SSA IR after AST finalization but before backend generation, significantly improving generated code quality.

### Backend Integration

Backend connectivity changed substantially with the introduction of XIR. Originally, backends in `src/backends/cuda/`, `src/backends/dx/`, and similar directories walked the AST tree directly to emit PTX, HLSL, MSL, or LLVM IR. Under IR v2, backends receive an XIR `Module` object after all IR passes complete, then lower this optimized representation to target-specific source code. This decoupling allows backend developers to focus on efficient translation rather than optimization logic.

## The IR v2 Compilation Pipeline

The complete transformation flow illustrates where IR v2 sits in the LuisaCompute architecture:

```

User DSL → FunctionBuilder (AST) → AST finalization → ast2xir translator → XIR (SSA + CFG) → IR passes → Backend codegen → Device binary

```

This pipeline, documented in [`docs/source/architecture.md`](https://github.com/luisagroup/luisacompute/blob/main/docs/source/architecture.md), shows that XIR construction occurs via the AST-to-XIR translator defined in [`include/luisa/xir/translators/ast2xir.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/ast2xir.h), specifically through the `ast2xir::translate()` function.

## Practical Implementation Examples

### Building Kernels with the Original AST System

The original workflow constructs AST nodes directly through the C++ DSL:

```cpp
using namespace luisa::compute;

auto kernel = [&](ImageFloat img) noexcept {
    Var<float2> coord = dispatch_id().xy();
    img->write(coord, make_float4(1.0f));
};

FunctionBuilder builder;
auto *fn = builder.create_function(kernel);   // AST is recorded here

```

This code located in [`include/luisa/ast/function_builder.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/ast/function_builder.h) creates the high-level tree structure that served as the compilation input in LuisaCompute v1.

### Translating AST to XIR and Running Optimizations

Conversion to IR v2 requires invoking the translator and executing optimization passes:

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

using namespace luisa::xir;

// Translate the finalized AST function into an XIR module
auto *module = ast2xir::translate(fn);

// Run the Mem2Reg pass (promotes stack allocations to registers)
mem2reg::run(*module);

// The module can now be handed to a backend, e.g. CUDA:
auto *shader = device.compile(module);

```

The `ast2xir::translate()` function transforms the finalized AST into an SSA-form XIR module, after which passes like `mem2reg::run()` optimize the representation before backend consumption.

### Inspecting the SSA and CFG Structure

Unlike the AST, XIR exposes explicit control flow and SSA values:

```cpp
for (auto &func : module->functions()) {
    std::cout << "Function: " << func.name() << "\n";
    for (auto &bb : func.basic_blocks()) {
        std::cout << "  BB#" << bb.id() << ":\n";
        for (auto *inst : bb.instructions()) {
            std::cout << "    " << inst->to_string() << "\n";
        }
    }
}

```

This inspection reveals `PhiInst` nodes for SSA merge points and explicit branch instructions—structures entirely absent from the original AST representation.

## Key Source Files in the IR v2 Architecture

Understanding the implementation requires familiarity with these specific files:

- **[`include/luisa/ast/function_builder.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/ast/function_builder.h)**: Core of the original AST construction system using `FunctionBuilder` and `Var<T>` objects.
- **[`include/luisa/xir/value.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/value.h)**: Defines the SSA `Value*` class hierarchy and XIR's fundamental data structures.
- **[`include/luisa/xir/translators/ast2xir.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/ast2xir.h)**: Implements the `ast2xir::translate()` function converting AST to XIR.
- **[`include/luisa/xir/passes/mem2reg.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/mem2reg.h)**: Example optimization pass promoting memory allocations to SSA registers.
- **`src/backends/cuda/`** and **`src/backends/dx/`**: Backend directories now consuming XIR modules rather than AST trees.
- **[`docs/source/architecture.md`](https://github.com/luisagroup/luisacompute/blob/main/docs/source/architecture.md)**: Official documentation outlining the v2 architectural flow.

## Summary

- **IR v2 (XIR)** replaces the direct AST-to-backend pipeline with an intermediate SSA-form representation featuring an explicit CFG.
- The **AST system** in `include/luisa/ast/*` provides high-level trees via `FunctionBuilder`, while **XIR** in `include/luisa/xir/*` provides low-level SSA values and basic blocks.
- **Translation** occurs through `ast2xir::translate()` in [`include/luisa/xir/translators/ast2xir.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/translators/ast2xir.h), converting finalized AST functions to XIR modules.
- **Optimization passes** like `mem2reg`, `dce`, and `autodiff` operate on XIR before backend code generation, unlike the original system where optimizations were backend-dependent.
- **Backends** now receive optimized XIR modules rather than walking AST trees, simplifying target-specific code generation in directories like `src/backends/cuda/`.

## Frequently Asked Questions

### What does SSA form mean in LuisaCompute IR v2?

SSA (Static Single Assignment) form means each variable is assigned exactly once, and every use refers to a specific definition. In the IR v2 architecture, XIR represents every value as a distinct `Value*` object, with phi nodes (`PhiInst`) handling merge points in the control flow. This enables efficient dataflow analysis and optimization passes that were impossible with the mutable `Var<T>` references in the original AST.

### Can I still use the original AST system in LuisaCompute v2?

The original AST remains the entry point for kernel definition through the C++ DSL and `FunctionBuilder`. However, the AST is now always translated to XIR via `ast2xir::translate()` before backend compilation. Direct AST-to-backend code generation without the XIR intermediate layer is no longer supported, as the IR v2 architecture is mandatory for the optimization pipeline.

### How do I add a custom optimization pass to IR v2?

Custom passes operate on XIR modules by iterating over functions, basic blocks, and instructions defined in [`include/luisa/xir/value.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/value.h). You create a pass function that transforms the module in-place, similar to `mem2reg::run()` in [`include/luisa/xir/passes/mem2reg.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/xir/passes/mem2reg.h). Because XIR uses standard SSA and CFG structures, you can implement classic compiler optimizations without modifying the core AST or backend code.

### Why did LuisaCompute switch from AST to IR v2?

The switch enables sophisticated optimizations like register promotion, dead code elimination, and automatic differentiation to run before backend translation. The original AST tree structure made complex dataflow analysis difficult, whereas IR v2's explicit CFG and SSA form allow independent optimization passes. This decoupling also makes the backend interface cleaner, allowing new targets to be added without reimplementing optimization logic.