Skip to content

Latest commit

 

History

History
233 lines (176 loc) · 7.34 KB

File metadata and controls

233 lines (176 loc) · 7.34 KB

Contributing to xDSL

This document describes the steps needed to clone, install, and test the repository, as well as a description of our coding and contributing conventions.

Contents

Developer Installation

We use uv for dependency management of xDSL. See uv's getting started page for more details.

# Ensure uv is installed
uv --version

Then, here are the commands to locally set up your development repository:

# Clone repo
git clone https://github.com/xdslproject/xdsl.git
cd xdsl

# Set up local environment with all optional and dev dependencies
# Creates a virtual environment called `.venv`
make venv
# Set up pre-commit hook for automatic formatting
make precommit-install
# Run all tests to verify installation was successful
make tests

Please take a look at the Makefile for the available commands such as running specific tests, running the documentation website locally, and others.

To make a custom mlir-opt available in the virtual environment, set the XDSL_MLIR_OPT_PATH variable when running make venv, like so:

XDSL_MLIR_OPT_PATH=/PATH/TO/LLVM/BUILD/bin/mlir-opt make venv

Alternative installations

For some use-cases, such as running xDSL with PyPy, it may be preferable to install a minimal set of dependencies instead. This can be done with uv sync. Note that Pyright will then complain about missing dependencies, so run make tests-functional instead of make tests to test the functionality of xDSL.

Testing and benchmarking

The xDSL project uses pytest unit tests, LLVM-style filecheck tests and performance benchmarks. They can be executed from the root directory with make tests (which runs everything except benchmarks and also runs pyright for type checking).

Unit Tests

Python tests in tests/ (excluding tests/filecheck) for testing APIs and logic:

# Run unit tests
uv run pytest
# or via makefile
make pytest

FileCheck Tests

File-based tests in tests/filecheck using filecheck (a Python reimplementation of LLVM's FileCheck) to verify tool output. These tests rely on the textual format to represent and construct IR. They are used to test that custom format implementations print and parse in the expected way, and to verify transformations such as pattern rewrites or passes:

# Run filecheck tests
uv run lit tests/filecheck
# or via makefile
make filecheck

Coverage Tests

To generate coverage reports for both unit tests and filecheck tests:

# Run coverage for unit tests and filecheck tests, then combine data files
make coverage
# Generate a coverage report
make coverage-report
# Or generate an HTML coverage report
make coverage-report-html

You can also run coverage for each test type individually:

# Run coverage for unit tests only
make coverage-tests
# Run coverage for filecheck tests only
make coverage-filecheck-tests

Benchmarks

Benchmarks for the project are tracked in the https://github.com/xdslproject/xdsl-bench repository. These run automatically every day on the main branch, reporting their results to https://xdsl.dev/xdsl-bench/. However, they can also be ran manually by cloning the repository and pointing the submodule at your feature branch to benchmark.

Formatting and Typechecking

Configuration for linting and formatting is found in pyproject.toml.

Ruff is used for linting and formatting. Configured in [tool.ruff].

Pyright is used for static type checking. Configured in [tool.pyright].

# Format code
uv run ruff format

# Type check code
uv run pyright
# or via makefile
make pyright

Important

Experimental Pyright Features

xDSL currently relies on an experimental feature of Pyright called TypeForm. TypeForm is in discussion and will likely land in some future version of Python.

For xDSL to type check correctly using Pyright, please add this to your pyproject.toml:

[tool.pyright]
enableExperimentalFeatures = true

Pre-commit Hooks

To automate the formatting and type checking, we use pre-commit hooks from the prek package, a drop-in replacement for pre-commit.

# Install the pre-commit on your `.git` folder
make precommit-install
# Run the hooks
make precommit

Code Style

We aim to follow these rules for all changes in this repository:

  • We aim for consistency in the code style and architectural patterns throughout the codebase in order to make it as easy as possible to understand and modify any part of xDSL.

  • We fix issues immediately rather than relying on future refactoring, as technical debt tends to accumulate and become harder to address over time.

  • We prefer simplicity: no code is better than obvious code, which is better than clever code. Premature abstraction often adds complexity without clear benefit.

  • We prioritize code locality over DRY (Don't Repeat Yourself). Keeping related logic close together - even if it results in slight duplication - makes it easier to understand code in isolation. We minimize variable scope.

  • We write self-describing code by using descriptive variable names and constant intermediary variables rather than relying heavily on comments.

  • We use guard-first logic, handling edge cases, invalid inputs and errors at the start of functions. Returning early keeps the "happy path" at the lowest indentation level, making the main logic easier to follow.

  • We keep if/else blocks small and avoid nesting beyond two levels when possible, as flat structures are easier to read and reason about.

  • We centralize control flow in parent functions, keeping leaf functions as pure logic. This separation makes the codebase more predictable and testable.

  • We access operation properties and attributes via the attribute shortcut op.someprop rather than op.properties["someprop"] or op.attributes["someprop"], as the shortcut is concise and benefits from static type checking.

  • We fail fast by detecting unexpected conditions immediately and raising exceptions rather than corrupting state, as this makes debugging easier.

  • We follow the Python philosophy of "ask for forgiveness not permission": assume keys and attributes exist and catch exceptions when they don't. For single-value lookups, prefer the walrus operator to avoid a double lookup:

    # Good: single lookup
    if (value := mapping.get(key)) is None:
        raise MyException()
    
    # Good: EAFP, when no sentinel is available
    try:
        return mapping[key]
    except KeyError:
        return default_value
    
    # Bad: LBYL, double lookup
    if key not in mapping:
        raise MyException()
    return mapping[key]

Discussion

You can also join the discussion at our Zulip chat room, kindly supported by community hosting from Zulip.