xDSL Documentation

repository·main·Indexed 20 days ago

https://github.com/xdslproject/xdsl

A Python-native framework for building compiler infrastructure using SSA-based intermediate representations (IRs). xDSL enables prototyping compilers in Python with compatibility with the MLIR/LLVM ecosystem, featuring tools for IR manipulation, dialect definition, and a Python front-end via PyASTContext for mapping Python to MLIR.

Tokens
21K
Snippets
66
Records
94
Agent score
66%

What's inside xDSL

  1. Understand the limitations of the xDSL Python front-end

    main

    The pyast front-end is an ongoing work with several current constraints:

    • Supported Structures: Supports simple operands and control flow. It currently does not support assignment (Assign/AnnAssign) or complex structures like classes.
    • Code Generation: The code_generation.py module walks the AST to generate xDSL, but it is currently limited in its scope.
    • PythonCodeCheck Constraints: The framework enforces specific structural rules via python_code_check.py:
      1. Code structures must not have nested blocks (though this is a target for improvement).
      2. Blocks must have explicit terminators.
      3. Constant values are guaranteed and inlined.
  2. Getting Started with xDSL

    main

    To begin using xDSL, you can follow these resources:

    • Comprehensive Tutorial: Visit the official Getting Started guide.
    • Tutorial Notebooks: Use the notebooks for hands-on examples covering core concepts like data structures, the Python-embedded abstraction definition language, and building end-to-end custom compilers.
    • MLIR Integration: For users interested in connecting xDSL with MLIR, refer to the MLIR interoperation guide.
  3. Install xDSL subprojects with extra dependencies

    main

    Certain xDSL subprojects require additional dependencies. These must be specified explicitly using extras. Examples include:

    • xdsl[gui]
    • xdsl[jax]
    • xdsl[riscv]
    pip install xdsl[gui]
  4. Set up MLIR interoperation for xDSL

    main

    xDSL can use MLIR as a backend to generate executables, but this functionality requires a local installation of the LLVM project and MLIR.

    1. Install MLIR: Clone and build MLIR following the official guide at https://mlir.llvm.org/getting_started/.
    2. Update PATH: Ensure that mlir-opt, mlir-translate, and clang are available in your system's PATH by adding the LLVM build directory to it.
    export PATH=<insert-your-path>/llvm-project/build/bin:$PATH
  5. Add new benchmarks to xDSL

    main

    To add a new benchmark, follow these three steps:

    1. Define the benchmark function

    Create a function or method named time_*. You have two patterns:

    Pattern A: Class-based (using BenchmarkClass) Use this if you need a shared setup method. Inherit from BenchmarkClass from benchmarks.bench_utils.

    Pattern B: Standalone function Use this for simple, module-scope functions. These do not inherit from BenchmarkClass.

    Note on @safe_to_repeat: If the benchmark call is independent and does not alter state that would affect subsequent runs (e.g., it only reads immutable inputs), decorate it with @safe_to_repeat from benchmarks.bench_utils.

    2. Define workloads (if necessary)

    If your benchmark requires specific xDSL operations or workloads, add them to benchmarks/workloads.py as a new class method on WorkloadBuilder.

    3. Register for local profiling

    Associate the benchmark with a name in the profile call at the bottom of your file using BenchmarkFunction from bench_utils.

    import importlib
    import xdsl.dialects.newdialect
    from benchmarks.bench_utils import BenchmarkClass, safe_to_repeat
    
    class ImportDialects(BenchmarkClass):
        @safe_to_repeat
        def time_newdialect_load(self) -> None:
            """Time loading the `newdialect` dialect.""
            importlib.reload(xdsl.dialects.newdialect)
    
    # To register for local profiling:
    from bench_utils import BenchmarkFunction, profile
    
    profile(
        {
            "Dialects.newdialect_load": BenchmarkFunction(time_newdialect_load),
        }
    )
  6. Set up xDSL for development

    main

    Before contributing or developing with xDSL, ensure you have performed a Developer Installation and configured Formatting and Typechecking as described in the main repository README.

    To verify your environment is correctly configured, run the following command in your terminal. All tests must pass before proceeding with development tasks.

    make tests
  7. Run xDSL notebooks using Marimo

    main

    xDSL provides introductory notebooks for compiler development using Marimo, a modern Python notebook that runs in your browser. Marimo maintains a dependency graph between cells, automatically re-running dependencies when a cell is changed.

    Note: When you open a notebook, it automatically installs xdsl in a browser-based environment. You may need to wait for the installation to complete and then re-run all cells to begin working.

  8. Run the xDSL Interactive GUI

    main

    The xDSL interactive application is a command-line tool used to explore and construct compiler pipelines visually. It allows you to input xDSL IR, select passes from a tree, and see the resulting IR and operation count changes in real-time.

    Standard Execution

    To run the GUI normally, execute the following command in your terminal:

    xdsl-gui

    Development Mode

    If you are developing or need to run in development mode, ensure you have textual-dev installed, then run:

    textual run xdsl.interactive.app:InputApp --dev
    xdsl-gui
  9. Run and profile benchmarks locally

    main

    Benchmarks are invoked by running the specific benchmark script (e.g., benchmarks/lexer.py) using uv run. The CLI requires two positional arguments: the name of the benchmark (or all) and the mode of execution.

    Available Modes:

    • run: Execute the benchmark.
    • timeit: Measure the execution time.
    • snakeviz, viztracer, flameprof, pyinstrument: Profiling tools.
    • dis: Disassemble the benchmark.

    Options:

    • -o, --output OUTPUT: Directory to write profile files.
    • -q, --quiet: Suppress the profiler's UI.
    uv run benchmarks/lexer.py Lexer.empty_program run
  10. Create a PyASTContext to map Python to MLIR

    main

    To embed MLIR into Python using the xDSL front-end, you must first instantiate a PyASTContext. This context acts as a registry that maps Python types, methods, and literals to their corresponding xDSL IR types, operations, and attributes.

    You must register the following for every element used in your program:

    • Types: Use register_type(python_type, xdsl_type).
    • Functions/Methods: Use register_function(python_callable, xdsl_op) to map Python operations (like __add__) to xDSL operations.
    • Literals: Use register_literal(python_type, factory_func) where the factory function returns an xDSL operation representing the constant (e.g., a ConstantOp).
    from xdsl.dialects.arith import AddfOp, ConstantOp, MulfOp
    from xdsl.dialects.builtin import FloatAttr, f64
    from xdsl.frontend.pyast.context import PyASTContext
    
    ctx = PyASTContext()
    ctx.register_type(float, f64)
    ctx.register_function(float.__add__, AddfOp)
    ctx.register_function(float.__mul__, MulfOp)
    ctx.register_literal(float, lambda v: ConstantOp(FloatAttr(v, 64)))