Skip to content

Re-do MLIR Python bindings hand-written over the C++ API (the eudsl-llvmpy way) #651

Description

@makslevental

eudsl-py (removed in #650) bound MLIR in Python by auto-generating nanobind bindings from MLIR C++ headers via eudsl-nbgen (a source-to-source translator driven mostly off TableGen-generated *.h.inc files). That approach turned out to be brittle and only translated a narrow slice of the API correctly.

This issue tracks doing MLIR Python bindings more correctly, applying the eudsl-llvmpy approach where it fits: hand-written nanobind wrappers over the core C++ API.

Note this is not an all-hand-written vs. all-generated choice. MLIR dialect ops are themselves auto-generated in C++ from TableGen (mlir-tblgen Op Definitions), so binding the dialects will necessarily involve some form of autogeneration too. The goal is to split the work correctly:

  • Core IR API — hand-written, the way eudsl-llvmpy does LLVM IR / Machine IR (Context/Module/Operation/Block/Region/Value/Type/Attribute, etc.). This stable, hand-authored surface is what eudsl-py's generated approach got wrong.
  • Dialect ops — generated, since the C++ for them is already generated from TableGen. The open question is the right generation strategy (drive off the ODS/TableGen records or the generated headers, emit nanobind directly, etc.), not whether to generate at all.

The eudsl-llvmpy conventions we'd want to carry over to the hand-written core (see projects/eudsl-llvmpy/CLAUDE.md), and ideally have the generated dialect layer respect as well:

  • Mirror the C++ API. Bind names as MLIR spells them; the Python class hierarchy mirrors the C++ one. Don't rename/merge methods because they read as equivalent.
  • Bind the full API; don't hardcode assumptions. Surface the C++ parameters (defaults are fine) rather than baking one choice into C++.
  • Prefer specific nanobind types over nb::object/nb::handle, so casts and identity checks are precise and is identity holds via nanobind's instance registry.
  • Breaking changes are fine — no stability contract; evolve the API when it improves things and update tests/docs to match.
  • Tests + coverage discipline as in eudsl-llvmpy (leak checks, per-PR C++ coverage over the binding sources).

Scope / open questions:

  • Which MLIR surfaces to bind first (IR core listed above), and the boundary between the hand-written core and the generated dialect layer.
  • The dialect-op generation strategy (source of truth: ODS/TableGen records vs. generated *.h.inc; what the emitted bindings look like) and how much can be shared with / learned from eudsl-nbgen.
  • Relationship to the upstream MLIR Python bindings (mlir-python-bindings) — where this complements vs. overlaps.

Follow-up to #650.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions