From 24569a525682a6dc6ed46fd443cce4354cefb2e8 Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:39:55 +0530 Subject: [PATCH 1/9] =?UTF-8?q?Add=20python=20bindings=20for=20the=20mlir?= =?UTF-8?q?=20QASM=E2=86=92QC=E2=86=92QCO=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + mlir/CMakeLists.txt | 7 ++ mlir/include/mqt-core-c/Dialects.h | 56 +++++++++++++++ mlir/lib/CAPI/CMakeLists.txt | 30 ++++++++ mlir/lib/CAPI/Dialects.cpp | 74 ++++++++++++++++++++ mlir/lib/CMakeLists.txt | 4 ++ mlir/python/CMakeLists.txt | 44 ++++++++++++ mlir/python/MQTCoreModule.cpp | 56 +++++++++++++++ python/mqt/core/mlir/__init__.py | 109 +++++++++++++++++++++++++++++ test/python/mlir/test_pipeline.py | 55 +++++++++++++++ 10 files changed, 436 insertions(+) create mode 100644 mlir/include/mqt-core-c/Dialects.h create mode 100644 mlir/lib/CAPI/CMakeLists.txt create mode 100644 mlir/lib/CAPI/Dialects.cpp create mode 100644 mlir/python/CMakeLists.txt create mode 100644 mlir/python/MQTCoreModule.cpp create mode 100644 python/mqt/core/mlir/__init__.py create mode 100644 test/python/mlir/test_pipeline.py diff --git a/.gitignore b/.gitignore index d6a4939889..3657671220 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ cmake-build-* # Distribution / packaging .Python /build/ +/build_*/ /test/*/build develop-eggs/ dist/ diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index cf6726f811..f2a759cd23 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -35,11 +35,18 @@ function(mqt_mlir_target_use_project_options target_name) endif() endfunction() +# Optionally expose the MLIR dialects and compilation pipeline to Python. +option(BUILD_MQT_CORE_MLIR_PYTHON "Build the Python bindings for the MQT Core MLIR dialects" OFF) + # add main library code add_subdirectory(include) add_subdirectory(lib) add_subdirectory(tools) +if(BUILD_MQT_CORE_MLIR_PYTHON) + add_subdirectory(python) +endif() + # add test code if(BUILD_MQT_CORE_TESTS) add_subdirectory(unittests) diff --git a/mlir/include/mqt-core-c/Dialects.h b/mlir/include/mqt-core-c/Dialects.h new file mode 100644 index 0000000000..409a27ee9e --- /dev/null +++ b/mlir/include/mqt-core-c/Dialects.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_C_DIALECTS_H +#define MQT_CORE_C_DIALECTS_H + +#include "mlir-c/IR.h" +#include "mlir-c/Support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(QC, qc); +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(QCO, qco); + +/** + * @brief Register and load all dialects required by the MQT compilation + * pipeline (QC, QCO, QTensor, and the upstream dialects they depend on). + * + * @param ctx The MLIR context to populate. + */ +MLIR_CAPI_EXPORTED void mqtRegisterDialects(MlirContext ctx); + +/** + * @brief Import an OpenQASM 3 program into a module of the QC dialect. + * + * @param ctx The MLIR context that owns the resulting module. The QC dialect + * must be registered with this context. + * @param qasm The OpenQASM 3 source program. + * @return The resulting QC-dialect module, or a null module on error. The + * caller owns the returned module. + */ +MLIR_CAPI_EXPORTED MlirModule mqtImportQASM3ToQC(MlirContext ctx, + MlirStringRef qasm); + +/** + * @brief Convert a QC-dialect module to the QCO dialect in place. + * + * @param module The module to convert. Modified in place on success. + * @return @c true if the conversion succeeded, @c false otherwise. + */ +MLIR_CAPI_EXPORTED bool mqtConvertQCToQCO(MlirModule module); + +#ifdef __cplusplus +} +#endif + +#endif // MQT_CORE_C_DIALECTS_H diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt new file mode 100644 index 0000000000..2124080512 --- /dev/null +++ b/mlir/lib/CAPI/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_public_c_api_library( + MQTCoreCAPI + Dialects.cpp + LINK_LIBS + PUBLIC + MLIRQCDialect + MLIRQCODialect + MLIRQTensorDialect + MLIRQCToQCO + MLIRQCTranslation + MQT::CoreQASM + MQT::CoreIR + MLIRIR + MLIRPass + MLIRArithDialect + MLIRControlFlowDialect + MLIRFuncDialect + MLIRLLVMDialect + MLIRMemRefDialect + MLIRSCFDialect) + +mqt_mlir_target_use_project_options(MQTCoreCAPI) diff --git a/mlir/lib/CAPI/Dialects.cpp b/mlir/lib/CAPI/Dialects.cpp new file mode 100644 index 0000000000..e95631f63b --- /dev/null +++ b/mlir/lib/CAPI/Dialects.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mqt-core-c/Dialects.h" + +#include "ir/QuantumComputation.hpp" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "qasm3/Importer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(QC, qc, mlir::qc::QCDialect) +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(QCO, qco, mlir::qco::QCODialect) + +void mqtRegisterDialects(MlirContext ctx) { + mlir::MLIRContext* context = unwrap(ctx); + mlir::DialectRegistry registry; + registry.insert(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); +} + +MlirModule mqtImportQASM3ToQC(MlirContext ctx, MlirStringRef qasm) { + mlir::MLIRContext* context = unwrap(ctx); + try { + const ::qc::QuantumComputation qc = + qasm3::Importer::imports(std::string(qasm.data, qasm.length)); + mlir::OwningOpRef module = + mlir::translateQuantumComputationToQC(context, qc); + if (!module) { + return MlirModule{nullptr}; + } + return wrap(module.release()); + } catch (const std::exception&) { + return MlirModule{nullptr}; + } +} + +bool mqtConvertQCToQCO(MlirModule module) { + mlir::ModuleOp moduleOp = unwrap(module); + mlir::PassManager pm(moduleOp.getContext()); + pm.addPass(mlir::createQCToQCO()); + return mlir::succeeded(pm.run(moduleOp)); +} diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 959d80e2f1..122dc339a4 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -10,3 +10,7 @@ add_subdirectory(Conversion) add_subdirectory(Compiler) add_subdirectory(Dialect) add_subdirectory(Support) + +if(BUILD_MQT_CORE_MLIR_PYTHON) + add_subdirectory(CAPI) +endif() diff --git a/mlir/python/CMakeLists.txt b/mlir/python/CMakeLists.txt new file mode 100644 index 0000000000..4f41e942eb --- /dev/null +++ b/mlir/python/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Make sure Python (with the development module component) is available for the +# nanobind extension. nanobind requires the Python::Module target. +if(NOT TARGET Python::Module) + find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module) +endif() + +# Locate nanobind (provided via the active Python environment). +if(NOT TARGET nanobind-static) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE nanobind_ROOT) + find_package(nanobind CONFIG REQUIRED) +endif() + +nanobind_add_module(MQTCoreMLIRPythonModule NB_SUPPRESS_WARNINGS MQTCoreModule.cpp) +set_target_properties(MQTCoreMLIRPythonModule PROPERTIES OUTPUT_NAME _mqtCore) +target_compile_features(MQTCoreMLIRPythonModule PRIVATE cxx_std_20) +target_link_libraries(MQTCoreMLIRPythonModule PRIVATE MQTCoreCAPI MLIRIR) +mqt_mlir_apply_target_options(MQTCoreMLIRPythonModule) + +# Place the extension next to the Python sources so it can be imported as +# mqt.core.mlir._mqtCore from the build tree. +set(MQT_CORE_MLIR_PYTHON_DIR ${CMAKE_BINARY_DIR}/python/mqt/core/mlir) +set_target_properties(MQTCoreMLIRPythonModule PROPERTIES LIBRARY_OUTPUT_DIRECTORY + ${MQT_CORE_MLIR_PYTHON_DIR}) + +# Copy the pure-Python package source next to the compiled extension so the +# package can be imported from the build tree . +add_custom_command( + TARGET MQTCoreMLIRPythonModule + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir/__init__.py ${MQT_CORE_MLIR_PYTHON_DIR}/__init__.py + COMMENT "Copying mqt.core.mlir Python source to the build tree" + VERBATIM) diff --git a/mlir/python/MQTCoreModule.cpp b/mlir/python/MQTCoreModule.cpp new file mode 100644 index 0000000000..774463247e --- /dev/null +++ b/mlir/python/MQTCoreModule.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mqt-core-c/Dialects.h" + +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; + +NB_MODULE(_mqtCore, m) { + m.doc() = "MQT Core MLIR dialects and compilation pipeline bindings"; + + m.def( + "register_dialects", + [](MlirContext context) { mqtRegisterDialects(context); }, + nb::arg("context"), + "Register and load all dialects used by the MQT compilation pipeline."); + + m.def( + "import_qasm3_to_qc", + [](MlirContext context, const std::string& qasm) { + const MlirModule module = mqtImportQASM3ToQC( + context, mlirStringRefCreate(qasm.data(), qasm.size())); + if (mlirModuleIsNull(module)) { + throw std::runtime_error( + "Failed to import OpenQASM 3 program into the QC dialect"); + } + return module; + }, + nb::arg("context"), nb::arg("qasm"), + "Import an OpenQASM 3 program into a QC-dialect module."); + + m.def( + "qc_to_qco", + [](MlirModule module) { + if (!mqtConvertQCToQCO(module)) { + throw std::runtime_error( + "Failed to transform the QC-dialect module to the QCO dialect"); + } + return module; + }, + nb::arg("module"), + "Transform a QC-dialect module to the QCO dialect in place."); +} diff --git a/python/mqt/core/mlir/__init__.py b/python/mqt/core/mlir/__init__.py new file mode 100644 index 0000000000..7c92ab77a4 --- /dev/null +++ b/python/mqt/core/mlir/__init__.py @@ -0,0 +1,109 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Python bindings for the MQT Core MLIR compilation pipeline. + +Exposes the (py:qasm) -> (mlir:qc) -> (mlir:qco) pipeline of the MQT +Compiler Collection to Python. The QC and QCO stages are returned as native +class: mlir.ir.Moduleobjects; the transformations themselves run in C++. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from mlir.ir import Context + +from ._mqtCore import import_qasm3_to_qc, qc_to_qco, register_dialects + +if TYPE_CHECKING: + from mlir.ir import Module + +__all__ = [ + "QASMProgram", + "create_context", + "read_qasm", + "transform_to_qco", + "translate_to_qc", +] + + +@dataclass(frozen=True) +class QASMProgram: + """An in-memory OpenQASM 3 program (the py:qasm stage of the pipeline). + + Attributes: + source: The OpenQASM 3 source code. + """ + + source: str + + +def create_context() -> Context: + """Create an MLIR context with all MQT pipeline dialects registered. + + Returns: + A fresh :class:`mlir.ir.Context` with the QC, QCO, and supporting + dialects registered and loaded. + """ + context = Context() + register_dialects(context) + return context + + +def read_qasm(source: str | Path) -> QASMProgram: + """Read an OpenQASM 3 program (py:qasm stage of the pipeline). + + Args: + source: Either an OpenQASM 3 source string or a path to a .qasm file. + + Returns: + The loaded program as a :class:`QASMProgram`. + """ + if isinstance(source, Path): + text = source.read_text(encoding="utf-8") + elif "\n" not in source and source.endswith(".qasm") and Path(source).is_file(): + text = Path(source).read_text(encoding="utf-8") + else: + text = source + return QASMProgram(source=text) + + +def translate_to_qc(program: QASMProgram | str, context: Context | None = None) -> Module: + """Translate an OpenQASM 3 program to the QC dialect (mlir:qc stage). + + Args: + program: The program to translate, as returned by :func:`read_qasm`, or + a raw OpenQASM 3 source string. + context: The MLIR context to own the resulting module. If None, a + new context with all pipeline dialects registered is created. + + Returns: + The quantum program as an :class: mlir.ir.Module in the QC dialect. + """ + qasm = program.source if isinstance(program, QASMProgram) else program + if context is None: + context = create_context() + return import_qasm3_to_qc(context, qasm) + + +def transform_to_qco(module: Module) -> Module: + """Transform a QC-dialect module to the QCO dialect (mlir:qco stage). + + Runs the qc-to-qco conversion pass on the module in place. + + Args: + module: A QC-dialect :class: mlir.ir.Module , as returned by + :func: translate_to_qc. + + Returns: + The same module, transformed to the QCO dialect. + """ + return qc_to_qco(module) diff --git a/test/python/mlir/test_pipeline.py b/test/python/mlir/test_pipeline.py new file mode 100644 index 0000000000..d11dad5a8a --- /dev/null +++ b/test/python/mlir/test_pipeline.py @@ -0,0 +1,55 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for the MQT Core MLIR Python pipeline bindings.""" + +from __future__ import annotations + +import pytest + +# The MLIR bindings require MQT Core to be built with BUILD_MQT_CORE_MLIR_PYTHON +# and the MLIR Python runtime to be importable. Skip the whole module otherwise. +pytest.importorskip("mlir.ir") +mqt_mlir = pytest.importorskip("mqt.core.mlir") + +BELL_QASM = """OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c; +h q[0]; +cx q[0], q[1]; +c[0] = measure q[0]; +c[1] = measure q[1]; +""" + + +def test_translate_to_qc_produces_qc_module() -> None: + """translate_to_qc translates an OpenQASM 3 program to a QC-dialect module.""" + program = mqt_mlir.read_qasm(BELL_QASM) + module = mqt_mlir.translate_to_qc(program) + ir = str(module) + assert "qc." in ir + # The Hadamard gate should be present in the QC dialect. + assert "qc.h" in ir + + +def test_transform_to_qco_produces_qco_module() -> None: + """transform_to_qco lowers a QC-dialect module to the QCO dialect.""" + qc_module = mqt_mlir.translate_to_qc(BELL_QASM) + qco_module = mqt_mlir.transform_to_qco(qc_module) + ir = str(qco_module) + assert "qco." in ir + + +def test_pipeline_shares_context() -> None: + """The QASM -> QC -> QCO pipeline runs end to end in one context.""" + context = mqt_mlir.create_context() + program = mqt_mlir.read_qasm(BELL_QASM) + qc_module = mqt_mlir.translate_to_qc(program, context=context) + qco_module = mqt_mlir.transform_to_qco(qc_module) + assert "qco." in str(qco_module) From 839ff42ef70cdf99765f5237be443f6ebbbb4a07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:15:44 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/CAPI/Dialects.cpp | 3 ++- mlir/python/CMakeLists.txt | 17 +++++++++-------- mlir/python/MQTCoreModule.cpp | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mlir/lib/CAPI/Dialects.cpp b/mlir/lib/CAPI/Dialects.cpp index e95631f63b..43d615d16e 100644 --- a/mlir/lib/CAPI/Dialects.cpp +++ b/mlir/lib/CAPI/Dialects.cpp @@ -18,7 +18,6 @@ #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "qasm3/Importer.hpp" -#include #include #include #include @@ -33,6 +32,8 @@ #include #include #include + +#include #include MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(QC, qc, mlir::qc::QCDialect) diff --git a/mlir/python/CMakeLists.txt b/mlir/python/CMakeLists.txt index 4f41e942eb..882d315258 100644 --- a/mlir/python/CMakeLists.txt +++ b/mlir/python/CMakeLists.txt @@ -6,8 +6,8 @@ # # Licensed under the MIT License -# Make sure Python (with the development module component) is available for the -# nanobind extension. nanobind requires the Python::Module target. +# Make sure Python (with the development module component) is available for the nanobind extension. +# nanobind requires the Python::Module target. if(NOT TARGET Python::Module) find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module) endif() @@ -27,18 +27,19 @@ target_compile_features(MQTCoreMLIRPythonModule PRIVATE cxx_std_20) target_link_libraries(MQTCoreMLIRPythonModule PRIVATE MQTCoreCAPI MLIRIR) mqt_mlir_apply_target_options(MQTCoreMLIRPythonModule) -# Place the extension next to the Python sources so it can be imported as -# mqt.core.mlir._mqtCore from the build tree. +# Place the extension next to the Python sources so it can be imported as mqt.core.mlir._mqtCore +# from the build tree. set(MQT_CORE_MLIR_PYTHON_DIR ${CMAKE_BINARY_DIR}/python/mqt/core/mlir) set_target_properties(MQTCoreMLIRPythonModule PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${MQT_CORE_MLIR_PYTHON_DIR}) -# Copy the pure-Python package source next to the compiled extension so the -# package can be imported from the build tree . +# Copy the pure-Python package source next to the compiled extension so the package can be imported +# from the build tree . add_custom_command( TARGET MQTCoreMLIRPythonModule POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir/__init__.py ${MQT_CORE_MLIR_PYTHON_DIR}/__init__.py + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir/__init__.py + ${MQT_CORE_MLIR_PYTHON_DIR}/__init__.py COMMENT "Copying mqt.core.mlir Python source to the build tree" VERBATIM) diff --git a/mlir/python/MQTCoreModule.cpp b/mlir/python/MQTCoreModule.cpp index 774463247e..cb6d4b8eba 100644 --- a/mlir/python/MQTCoreModule.cpp +++ b/mlir/python/MQTCoreModule.cpp @@ -14,6 +14,7 @@ #include #include #include + #include #include From e3b654fe6e10779e962bf18ad569dfbd93fbc6ae Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sat, 13 Jun 2026 09:57:35 +0530 Subject: [PATCH 3/9] Expose all mqt dialects and passes; move bindings into bindings/mlir --- bindings/CMakeLists.txt | 5 + bindings/mlir/.clang-tidy | 13 ++ bindings/mlir/CMakeLists.txt | 34 +++++ .../mlir/register_mlir.cpp | 36 ++--- mlir/CMakeLists.txt | 7 - .../mqt-core-c/{Dialects.h => Registration.h} | 38 ++--- mlir/lib/CAPI/CMakeLists.txt | 17 ++- .../CAPI/{Dialects.cpp => Registration.cpp} | 57 ++++--- mlir/lib/CMakeLists.txt | 3 +- mlir/python/CMakeLists.txt | 45 ------ python/mqt/core/mlir/__init__.py | 102 ++----------- python/mqt/core/mlir/pipeline.py | 139 ++++++++++++++++++ test/python/mlir/test_pipeline.py | 30 +++- 13 files changed, 321 insertions(+), 205 deletions(-) create mode 100644 bindings/mlir/.clang-tidy create mode 100644 bindings/mlir/CMakeLists.txt rename mlir/python/MQTCoreModule.cpp => bindings/mlir/register_mlir.cpp (60%) rename mlir/include/mqt-core-c/{Dialects.h => Registration.h} (51%) rename mlir/lib/CAPI/{Dialects.cpp => Registration.cpp} (55%) delete mode 100644 mlir/python/CMakeLists.txt create mode 100644 python/mqt/core/mlir/pipeline.py diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 2ba92c4400..ea15836b16 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -10,3 +10,8 @@ add_subdirectory(ir) add_subdirectory(dd) add_subdirectory(fomac) add_subdirectory(na) + +# The MLIR bindings require the MLIR submodule to be built. +if(BUILD_MQT_CORE_MLIR) + add_subdirectory(mlir) +endif() diff --git a/bindings/mlir/.clang-tidy b/bindings/mlir/.clang-tidy new file mode 100644 index 0000000000..bd71412f6a --- /dev/null +++ b/bindings/mlir/.clang-tidy @@ -0,0 +1,13 @@ +# This binding module uses the NB_MODULE macro and the MLIR C-API type-casters +# from NanobindAdaptors.h. The include-cleaner, internal-linkage, +# identifier-naming, named-parameter, value-parameter, and unused-alias checks +# cannot reason about the macro expansion and the templated casters, so they +# report false positives here. Disable them for this binding glue. +InheritParentConfig: true +Checks: | + -misc-include-cleaner, + -misc-unused-alias-decls, + -misc-use-internal-linkage, + -performance-unnecessary-value-param, + -readability-identifier-naming, + -readability-named-parameter diff --git a/bindings/mlir/CMakeLists.txt b/bindings/mlir/CMakeLists.txt new file mode 100644 index 0000000000..312921fd17 --- /dev/null +++ b/bindings/mlir/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +if(NOT TARGET ${MQT_CORE_TARGET_NAME}-mlir-bindings) + # collect source files + file(GLOB_RECURSE MLIR_BINDINGS_SOURCES **.cpp) + + # declare the Python module + add_mqt_python_binding_nanobind( + CORE + ${MQT_CORE_TARGET_NAME}-mlir-bindings + ${MLIR_BINDINGS_SOURCES} + MODULE_NAME + _mlir + INSTALL_DIR + ./mlir + LINK_LIBS + MQTCoreCAPI + MLIRIR) + + # install the Python stub files in editable mode for better IDE support + if(SKBUILD_STATE STREQUAL "editable") + file(GLOB_RECURSE MLIR_PYI_FILES ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir/*.pyi) + install( + FILES ${MLIR_PYI_FILES} + DESTINATION ./mlir + COMPONENT ${MQT_CORE_TARGET_NAME}_Python) + endif() +endif() diff --git a/mlir/python/MQTCoreModule.cpp b/bindings/mlir/register_mlir.cpp similarity index 60% rename from mlir/python/MQTCoreModule.cpp rename to bindings/mlir/register_mlir.cpp index cb6d4b8eba..95ef78e1a0 100644 --- a/mlir/python/MQTCoreModule.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mqt-core-c/Dialects.h" +#include "mqt-core-c/Registration.h" #include #include @@ -18,16 +18,28 @@ #include #include +namespace mqt { + namespace nb = nanobind; -NB_MODULE(_mqtCore, m) { - m.doc() = "MQT Core MLIR dialects and compilation pipeline bindings"; +NB_MODULE(MQT_CORE_MODULE_NAME, m) { + m.doc() = + R"pb(MQT Core MLIR - Python bindings for the MQT Compiler Collection. + +This module exposes the dialects and passes of the MQT Compiler Collection so +that quantum programs can be compiled from Python using MLIR's Python bindings.)pb"; m.def( "register_dialects", - [](MlirContext context) { mqtRegisterDialects(context); }, + [](MlirContext context) { mqtRegisterAllDialects(context); }, nb::arg("context"), - "Register and load all dialects used by the MQT compilation pipeline."); + "Register and load all MQT Compiler Collection dialects with the given " + "context."); + + m.def( + "register_passes", []() { mqtRegisterAllPasses(); }, + "Register all MQT Compiler Collection passes with MLIR's global pass " + "registry."); m.def( "import_qasm3_to_qc", @@ -42,16 +54,6 @@ NB_MODULE(_mqtCore, m) { }, nb::arg("context"), nb::arg("qasm"), "Import an OpenQASM 3 program into a QC-dialect module."); - - m.def( - "qc_to_qco", - [](MlirModule module) { - if (!mqtConvertQCToQCO(module)) { - throw std::runtime_error( - "Failed to transform the QC-dialect module to the QCO dialect"); - } - return module; - }, - nb::arg("module"), - "Transform a QC-dialect module to the QCO dialect in place."); } + +} // namespace mqt diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index f2a759cd23..cf6726f811 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -35,18 +35,11 @@ function(mqt_mlir_target_use_project_options target_name) endif() endfunction() -# Optionally expose the MLIR dialects and compilation pipeline to Python. -option(BUILD_MQT_CORE_MLIR_PYTHON "Build the Python bindings for the MQT Core MLIR dialects" OFF) - # add main library code add_subdirectory(include) add_subdirectory(lib) add_subdirectory(tools) -if(BUILD_MQT_CORE_MLIR_PYTHON) - add_subdirectory(python) -endif() - # add test code if(BUILD_MQT_CORE_TESTS) add_subdirectory(unittests) diff --git a/mlir/include/mqt-core-c/Dialects.h b/mlir/include/mqt-core-c/Registration.h similarity index 51% rename from mlir/include/mqt-core-c/Dialects.h rename to mlir/include/mqt-core-c/Registration.h index 409a27ee9e..54667c22de 100644 --- a/mlir/include/mqt-core-c/Dialects.h +++ b/mlir/include/mqt-core-c/Registration.h @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_C_DIALECTS_H -#define MQT_CORE_C_DIALECTS_H +#ifndef MQT_CORE_C_REGISTRATION_H +#define MQT_CORE_C_REGISTRATION_H #include "mlir-c/IR.h" #include "mlir-c/Support.h" @@ -18,16 +18,28 @@ extern "C" { #endif -MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(QC, qc); -MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(QCO, qco); - /** - * @brief Register and load all dialects required by the MQT compilation - * pipeline (QC, QCO, QTensor, and the upstream dialects they depend on). + * @brief Register and load all dialects of the MQT Compiler Collection with the + * given context. + * + * @details Registers the QC, QCO, QTensor, and Jeff dialects together with the + * upstream dialects (arith, func, scf, cf, memref, llvm) that the dialects, + * conversions, and transformations depend on, and loads them so that modules + * and passes can use them. * * @param ctx The MLIR context to populate. */ -MLIR_CAPI_EXPORTED void mqtRegisterDialects(MlirContext ctx); +MLIR_CAPI_EXPORTED void mqtRegisterAllDialects(MlirContext ctx); + +/** + * @brief Register all conversion and transformation passes of the MQT Compiler + * Collection with MLIR's global pass registry. + * + * @details After this call, every MQT pass can be instantiated by name (e.g. + * via a textual pass pipeline) from Python through the standard MLIR + * PassManager API. + */ +MLIR_CAPI_EXPORTED void mqtRegisterAllPasses(void); /** * @brief Import an OpenQASM 3 program into a module of the QC dialect. @@ -41,16 +53,8 @@ MLIR_CAPI_EXPORTED void mqtRegisterDialects(MlirContext ctx); MLIR_CAPI_EXPORTED MlirModule mqtImportQASM3ToQC(MlirContext ctx, MlirStringRef qasm); -/** - * @brief Convert a QC-dialect module to the QCO dialect in place. - * - * @param module The module to convert. Modified in place on success. - * @return @c true if the conversion succeeded, @c false otherwise. - */ -MLIR_CAPI_EXPORTED bool mqtConvertQCToQCO(MlirModule module); - #ifdef __cplusplus } #endif -#endif // MQT_CORE_C_DIALECTS_H +#endif // MQT_CORE_C_REGISTRATION_H diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt index 2124080512..9b1234eca0 100644 --- a/mlir/lib/CAPI/CMakeLists.txt +++ b/mlir/lib/CAPI/CMakeLists.txt @@ -8,18 +8,33 @@ add_mlir_public_c_api_library( MQTCoreCAPI - Dialects.cpp + Registration.cpp LINK_LIBS PUBLIC + # Dialects MLIRQCDialect MLIRQCODialect MLIRQTensorDialect + MLIRJeff + # Conversions MLIRQCToQCO + MLIRQCOToQC + MLIRQCToQIR + MLIRJeffToQCO + MLIRQCOToJeff + # Transformations + MLIRQCTransforms + MLIRQCOTransforms + MLIRQIRTransforms + MLIRQTensorTransforms + # OpenQASM 3 import MLIRQCTranslation MQT::CoreQASM MQT::CoreIR + # Upstream MLIRIR MLIRPass + MLIRTransforms MLIRArithDialect MLIRControlFlowDialect MLIRFuncDialect diff --git a/mlir/lib/CAPI/Dialects.cpp b/mlir/lib/CAPI/Registration.cpp similarity index 55% rename from mlir/lib/CAPI/Dialects.cpp rename to mlir/lib/CAPI/Registration.cpp index 43d615d16e..89a5bf7a4e 100644 --- a/mlir/lib/CAPI/Dialects.cpp +++ b/mlir/lib/CAPI/Registration.cpp @@ -8,18 +8,27 @@ * Licensed under the MIT License */ -#include "mqt-core-c/Dialects.h" +#include "mqt-core-c/Registration.h" #include "ir/QuantumComputation.hpp" +#include "mlir/Conversion/JeffToQCO/JeffToQCO.h" +#include "mlir/Conversion/QCOToJeff/QCOToJeff.h" +#include "mlir/Conversion/QCOToQC/QCOToQC.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Conversion/QCToQIR/QCToQIR.h" #include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Transforms/Passes.h" #include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QIR/Transforms/Passes.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "mlir/Dialect/QTensor/Transforms/Passes.h" #include "qasm3/Importer.hpp" +#include +#include #include -#include #include #include #include @@ -30,27 +39,40 @@ #include #include #include -#include -#include - -#include +#include #include -MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(QC, qc, mlir::qc::QCDialect) -MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(QCO, qco, mlir::qco::QCODialect) - -void mqtRegisterDialects(MlirContext ctx) { +void mqtRegisterAllDialects(MlirContext ctx) { mlir::MLIRContext* context = unwrap(ctx); mlir::DialectRegistry registry; registry.insert(); + mlir::qtensor::QTensorDialect, mlir::jeff::JeffDialect, + mlir::arith::ArithDialect, mlir::cf::ControlFlowDialect, + mlir::func::FuncDialect, mlir::LLVM::LLVMDialect, + mlir::memref::MemRefDialect, mlir::scf::SCFDialect>(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); } +void mqtRegisterAllPasses() { + // Common upstream transformations (canonicalization, CSE, ...) used by the + // MQT cleanup pipelines. + mlir::registerTransformsPasses(); + + // Conversions between the MQT dialects. + mlir::registerQCToQCOPasses(); + mlir::registerQCOToQCPasses(); + mlir::registerQCToQIRPasses(); + mlir::registerJeffToQCOPasses(); + mlir::registerQCOToJeffPasses(); + + // Dialect-specific transformations. + mlir::qc::registerQCPasses(); + mlir::qco::registerQCOPasses(); + mlir::qir::registerQIRPasses(); + mlir::qtensor::registerQTensorPasses(); +} + MlirModule mqtImportQASM3ToQC(MlirContext ctx, MlirStringRef qasm) { mlir::MLIRContext* context = unwrap(ctx); try { @@ -66,10 +88,3 @@ MlirModule mqtImportQASM3ToQC(MlirContext ctx, MlirStringRef qasm) { return MlirModule{nullptr}; } } - -bool mqtConvertQCToQCO(MlirModule module) { - mlir::ModuleOp moduleOp = unwrap(module); - mlir::PassManager pm(moduleOp.getContext()); - pm.addPass(mlir::createQCToQCO()); - return mlir::succeeded(pm.run(moduleOp)); -} diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 122dc339a4..6681556327 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(Compiler) add_subdirectory(Dialect) add_subdirectory(Support) -if(BUILD_MQT_CORE_MLIR_PYTHON) +# The C-API library backs the Python bindings. +if(BUILD_MQT_CORE_BINDINGS) add_subdirectory(CAPI) endif() diff --git a/mlir/python/CMakeLists.txt b/mlir/python/CMakeLists.txt deleted file mode 100644 index 882d315258..0000000000 --- a/mlir/python/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -# Make sure Python (with the development module component) is available for the nanobind extension. -# nanobind requires the Python::Module target. -if(NOT TARGET Python::Module) - find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module) -endif() - -# Locate nanobind (provided via the active Python environment). -if(NOT TARGET nanobind-static) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE nanobind_ROOT) - find_package(nanobind CONFIG REQUIRED) -endif() - -nanobind_add_module(MQTCoreMLIRPythonModule NB_SUPPRESS_WARNINGS MQTCoreModule.cpp) -set_target_properties(MQTCoreMLIRPythonModule PROPERTIES OUTPUT_NAME _mqtCore) -target_compile_features(MQTCoreMLIRPythonModule PRIVATE cxx_std_20) -target_link_libraries(MQTCoreMLIRPythonModule PRIVATE MQTCoreCAPI MLIRIR) -mqt_mlir_apply_target_options(MQTCoreMLIRPythonModule) - -# Place the extension next to the Python sources so it can be imported as mqt.core.mlir._mqtCore -# from the build tree. -set(MQT_CORE_MLIR_PYTHON_DIR ${CMAKE_BINARY_DIR}/python/mqt/core/mlir) -set_target_properties(MQTCoreMLIRPythonModule PROPERTIES LIBRARY_OUTPUT_DIRECTORY - ${MQT_CORE_MLIR_PYTHON_DIR}) - -# Copy the pure-Python package source next to the compiled extension so the package can be imported -# from the build tree . -add_custom_command( - TARGET MQTCoreMLIRPythonModule - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir/__init__.py - ${MQT_CORE_MLIR_PYTHON_DIR}/__init__.py - COMMENT "Copying mqt.core.mlir Python source to the build tree" - VERBATIM) diff --git a/python/mqt/core/mlir/__init__.py b/python/mqt/core/mlir/__init__.py index 7c92ab77a4..1acfc72afb 100644 --- a/python/mqt/core/mlir/__init__.py +++ b/python/mqt/core/mlir/__init__.py @@ -6,104 +6,30 @@ # # Licensed under the MIT License -"""Python bindings for the MQT Core MLIR compilation pipeline. +"""Python bindings for the MQT Compiler Collection. -Exposes the (py:qasm) -> (mlir:qc) -> (mlir:qco) pipeline of the MQT -Compiler Collection to Python. The QC and QCO stages are returned as native -class: mlir.ir.Moduleobjects; the transformations themselves run in C++. +Exposes the dialects and passes of the MQT Compiler Collection to Python via +MLIR's Python bindings. OpenQASM 3 programs are imported into the QC dialect and +returned as native :class:`mlir.ir.Module` objects, which can then be run +through any MQT pass pipeline. """ from __future__ import annotations -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING - -from mlir.ir import Context - -from ._mqtCore import import_qasm3_to_qc, qc_to_qco, register_dialects - -if TYPE_CHECKING: - from mlir.ir import Module +from .pipeline import ( + QASMProgram, + create_context, + read_qasm, + run_pipeline, + transform_to_qco, + translate_to_qc, +) __all__ = [ "QASMProgram", "create_context", "read_qasm", + "run_pipeline", "transform_to_qco", "translate_to_qc", ] - - -@dataclass(frozen=True) -class QASMProgram: - """An in-memory OpenQASM 3 program (the py:qasm stage of the pipeline). - - Attributes: - source: The OpenQASM 3 source code. - """ - - source: str - - -def create_context() -> Context: - """Create an MLIR context with all MQT pipeline dialects registered. - - Returns: - A fresh :class:`mlir.ir.Context` with the QC, QCO, and supporting - dialects registered and loaded. - """ - context = Context() - register_dialects(context) - return context - - -def read_qasm(source: str | Path) -> QASMProgram: - """Read an OpenQASM 3 program (py:qasm stage of the pipeline). - - Args: - source: Either an OpenQASM 3 source string or a path to a .qasm file. - - Returns: - The loaded program as a :class:`QASMProgram`. - """ - if isinstance(source, Path): - text = source.read_text(encoding="utf-8") - elif "\n" not in source and source.endswith(".qasm") and Path(source).is_file(): - text = Path(source).read_text(encoding="utf-8") - else: - text = source - return QASMProgram(source=text) - - -def translate_to_qc(program: QASMProgram | str, context: Context | None = None) -> Module: - """Translate an OpenQASM 3 program to the QC dialect (mlir:qc stage). - - Args: - program: The program to translate, as returned by :func:`read_qasm`, or - a raw OpenQASM 3 source string. - context: The MLIR context to own the resulting module. If None, a - new context with all pipeline dialects registered is created. - - Returns: - The quantum program as an :class: mlir.ir.Module in the QC dialect. - """ - qasm = program.source if isinstance(program, QASMProgram) else program - if context is None: - context = create_context() - return import_qasm3_to_qc(context, qasm) - - -def transform_to_qco(module: Module) -> Module: - """Transform a QC-dialect module to the QCO dialect (mlir:qco stage). - - Runs the qc-to-qco conversion pass on the module in place. - - Args: - module: A QC-dialect :class: mlir.ir.Module , as returned by - :func: translate_to_qc. - - Returns: - The same module, transformed to the QCO dialect. - """ - return qc_to_qco(module) diff --git a/python/mqt/core/mlir/pipeline.py b/python/mqt/core/mlir/pipeline.py new file mode 100644 index 0000000000..44d19db97f --- /dev/null +++ b/python/mqt/core/mlir/pipeline.py @@ -0,0 +1,139 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""The MQT Compiler Collection pipeline exposed through MLIR's Python bindings. + +OpenQASM 3 programs are imported into the QC dialect, and any registered MQT +pass or pass pipeline can be run on the resulting :class:`mlir.ir.Module` via +:func:`run_pipeline`. The heavy lifting (parsing, conversions, optimizations) +runs in C++. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from mlir.ir import Context # ty: ignore[unresolved-import] +from mlir.passmanager import PassManager # ty: ignore[unresolved-import] + +from ._mlir import ( # ty: ignore[unresolved-import] + import_qasm3_to_qc, + register_dialects, + register_passes, +) + +if TYPE_CHECKING: + from mlir.ir import Module # ty: ignore[unresolved-import] + +__all__ = [ + "QASMProgram", + "create_context", + "read_qasm", + "run_pipeline", + "transform_to_qco", + "translate_to_qc", +] + +# Passes are registered with MLIR's global registry exactly once per process. +register_passes() + + +@dataclass(frozen=True) +class QASMProgram: + """An in-memory OpenQASM 3 program (the ``py:qasm`` stage of the pipeline). + + Attributes: + source: The OpenQASM 3 source code. + """ + + source: str + + +def create_context() -> Context: + """Create an MLIR context with all MQT Compiler Collection dialects registered. + + Returns: + A fresh :class:`mlir.ir.Context` with the QC, QCO, QTensor, Jeff, and + supporting dialects registered and loaded. + """ + context = Context() + register_dialects(context) + return context + + +def read_qasm(source: str | Path) -> QASMProgram: + """Read an OpenQASM 3 program (the ``py:qasm`` stage of the pipeline). + + Args: + source: Either an OpenQASM 3 source string or a path to a ``.qasm`` + file. + + Returns: + The loaded program as a :class:`QASMProgram`. + """ + if isinstance(source, Path): + text = source.read_text(encoding="utf-8") + elif "\n" not in source and source.endswith(".qasm") and Path(source).is_file(): + text = Path(source).read_text(encoding="utf-8") + else: + text = source + return QASMProgram(source=text) + + +def translate_to_qc(program: QASMProgram | str, context: Context | None = None) -> Module: + """Translate an OpenQASM 3 program to the QC dialect (``mlir:qc`` stage). + + Args: + program: The program to translate, as returned by :func:`read_qasm`, or + a raw OpenQASM 3 source string. + context: The MLIR context to own the resulting module. If ``None``, a + new context with all dialects registered is created. + + Returns: + The quantum program as an :class:`mlir.ir.Module` in the QC dialect. + """ + qasm = program.source if isinstance(program, QASMProgram) else program + if context is None: + context = create_context() + return import_qasm3_to_qc(context, qasm) + + +def run_pipeline(module: Module, pipeline: str) -> Module: + """Run a textual MLIR pass pipeline on a module in place. + + Any pass of the MQT Compiler Collection (e.g. ``qc-to-qco``, + ``hadamard-lifting``, ``qc-to-qir``) can be used, as well as their + combinations. + + Args: + module: The :class:`mlir.ir.Module` to transform. + pipeline: A textual pass pipeline, e.g. ``"builtin.module(qc-to-qco)"``. + + Returns: + The same module, transformed by the pipeline. + """ + pass_manager = PassManager.parse(pipeline, context=module.context) + pass_manager.run(module.operation) + return module + + +def transform_to_qco(module: Module) -> Module: + """Transform a QC-dialect module to the QCO dialect (``mlir:qco`` stage). + + Convenience wrapper around :func:`run_pipeline` with the ``qc-to-qco`` pass. + + Args: + module: A QC-dialect :class:`mlir.ir.Module`, as returned by + :func:`translate_to_qc`. + + Returns: + The same module, transformed to the QCO dialect. + """ + return run_pipeline(module, "builtin.module(qc-to-qco)") diff --git a/test/python/mlir/test_pipeline.py b/test/python/mlir/test_pipeline.py index d11dad5a8a..342f1768d6 100644 --- a/test/python/mlir/test_pipeline.py +++ b/test/python/mlir/test_pipeline.py @@ -6,14 +6,14 @@ # # Licensed under the MIT License -"""Tests for the MQT Core MLIR Python pipeline bindings.""" +"""Tests for the MQT Compiler Collection Python bindings.""" from __future__ import annotations import pytest -# The MLIR bindings require MQT Core to be built with BUILD_MQT_CORE_MLIR_PYTHON -# and the MLIR Python runtime to be importable. Skip the whole module otherwise. +# The MLIR bindings require MQT Core to be built with the bindings and MLIR +# enabled, and the MLIR Python runtime to be importable. Skip otherwise. pytest.importorskip("mlir.ir") mqt_mlir = pytest.importorskip("mqt.core.mlir") @@ -29,21 +29,35 @@ def test_translate_to_qc_produces_qc_module() -> None: - """translate_to_qc translates an OpenQASM 3 program to a QC-dialect module.""" + """``translate_to_qc`` translates an OpenQASM 3 program to a QC-dialect module.""" program = mqt_mlir.read_qasm(BELL_QASM) module = mqt_mlir.translate_to_qc(program) ir = str(module) assert "qc." in ir - # The Hadamard gate should be present in the QC dialect. assert "qc.h" in ir def test_transform_to_qco_produces_qco_module() -> None: - """transform_to_qco lowers a QC-dialect module to the QCO dialect.""" + """``transform_to_qco`` lowers a QC-dialect module to the QCO dialect.""" qc_module = mqt_mlir.translate_to_qc(BELL_QASM) qco_module = mqt_mlir.transform_to_qco(qc_module) - ir = str(qco_module) - assert "qco." in ir + assert "qco." in str(qco_module) + + +def test_run_pipeline_round_trips_qc_to_qco_to_qc() -> None: + """``run_pipeline`` runs arbitrary MQT passes; QC -> QCO -> QC round-trips.""" + module = mqt_mlir.translate_to_qc(BELL_QASM) + mqt_mlir.run_pipeline(module, "builtin.module(qc-to-qco)") + assert "qco." in str(module) + mqt_mlir.run_pipeline(module, "builtin.module(qco-to-qc)") + assert "qc." in str(module) + + +def test_run_pipeline_rejects_unknown_pass() -> None: + """An unknown pass name in a pipeline raises an error.""" + module = mqt_mlir.translate_to_qc(BELL_QASM) + with pytest.raises((ValueError, RuntimeError)): + mqt_mlir.run_pipeline(module, "builtin.module(this-pass-does-not-exist)") def test_pipeline_shares_context() -> None: From 6054f69b292f578b0efb52723800ec0c95ea742e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 04:33:53 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/CAPI/Registration.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/CAPI/Registration.cpp b/mlir/lib/CAPI/Registration.cpp index 89a5bf7a4e..3e425a78f8 100644 --- a/mlir/lib/CAPI/Registration.cpp +++ b/mlir/lib/CAPI/Registration.cpp @@ -26,7 +26,6 @@ #include "mlir/Dialect/QTensor/Transforms/Passes.h" #include "qasm3/Importer.hpp" -#include #include #include #include @@ -40,6 +39,8 @@ #include #include #include + +#include #include void mqtRegisterAllDialects(MlirContext ctx) { From 60909494b5ab8e71f34e3281245c4d4d13ad1ff1 Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:19:19 +0530 Subject: [PATCH 5/9] use a plain static library for the MLIR C-API to fix configure across toolchains --- mlir/lib/CAPI/CMakeLists.txt | 87 ++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt index 9b1234eca0..2dd03338fc 100644 --- a/mlir/lib/CAPI/CMakeLists.txt +++ b/mlir/lib/CAPI/CMakeLists.txt @@ -6,40 +6,59 @@ # # Licensed under the MIT License -add_mlir_public_c_api_library( +# The C-API library backs the Python bindings. It is a plain static library (rather than +# add_mlir_public_c_api_library) so that it does not eagerly require its linked MLIR libraries to be +# defined targets at configure time, which is not guaranteed across all supported MLIR toolchains. +add_library(MQTCoreCAPI STATIC Registration.cpp) + +# Export the C-API symbols from the object files. +target_compile_definitions(MQTCoreCAPI PRIVATE MLIR_CAPI_BUILDING_LIBRARY=1) + +# The TableGen'd pass/conversion headers must be generated before compiling. +add_dependencies( + MQTCoreCAPI + QCToQCOIncGen + QCOToQCIncGen + QCToQIRIncGen + JeffToQCOIncGen + QCOToJeffIncGen + MLIRQCTransformsIncGen + MLIRQCOTransformsIncGen + MLIRQIRTransformsIncGen + MLIRQTensorTransformsIncGen) + +target_link_libraries( MQTCoreCAPI - Registration.cpp - LINK_LIBS - PUBLIC - # Dialects - MLIRQCDialect - MLIRQCODialect - MLIRQTensorDialect - MLIRJeff - # Conversions - MLIRQCToQCO - MLIRQCOToQC - MLIRQCToQIR - MLIRJeffToQCO - MLIRQCOToJeff - # Transformations - MLIRQCTransforms - MLIRQCOTransforms - MLIRQIRTransforms - MLIRQTensorTransforms - # OpenQASM 3 import - MLIRQCTranslation - MQT::CoreQASM - MQT::CoreIR - # Upstream - MLIRIR - MLIRPass - MLIRTransforms - MLIRArithDialect - MLIRControlFlowDialect - MLIRFuncDialect - MLIRLLVMDialect - MLIRMemRefDialect - MLIRSCFDialect) + PUBLIC # Dialects + MLIRQCDialect + MLIRQCODialect + MLIRQTensorDialect + MLIRJeff + # Conversions + MLIRQCToQCO + MLIRQCOToQC + MLIRQCToQIR + MLIRJeffToQCO + MLIRQCOToJeff + # Transformations + MLIRQCTransforms + MLIRQCOTransforms + MLIRQIRTransforms + MLIRQTensorTransforms + # OpenQASM 3 import + MLIRQCTranslation + MQT::CoreQASM + MQT::CoreIR + # Upstream + MLIRCAPIIR + MLIRIR + MLIRPass + MLIRTransforms + MLIRArithDialect + MLIRControlFlowDialect + MLIRFuncDialect + MLIRLLVMDialect + MLIRMemRefDialect + MLIRSCFDialect) mqt_mlir_target_use_project_options(MQTCoreCAPI) From c2a964c0afbac62751bd0ef0b53f3ea2ea21d92c Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sat, 13 Jun 2026 17:18:27 +0530 Subject: [PATCH 6/9] Guard MLIR C-API tablegen dependencies behind target checks --- mlir/lib/CAPI/CMakeLists.txt | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt index 2dd03338fc..2f57ce59db 100644 --- a/mlir/lib/CAPI/CMakeLists.txt +++ b/mlir/lib/CAPI/CMakeLists.txt @@ -14,18 +14,25 @@ add_library(MQTCoreCAPI STATIC Registration.cpp) # Export the C-API symbols from the object files. target_compile_definitions(MQTCoreCAPI PRIVATE MLIR_CAPI_BUILDING_LIBRARY=1) -# The TableGen'd pass/conversion headers must be generated before compiling. -add_dependencies( - MQTCoreCAPI - QCToQCOIncGen - QCOToQCIncGen - QCToQIRIncGen - JeffToQCOIncGen - QCOToJeffIncGen - MLIRQCTransformsIncGen - MLIRQCOTransformsIncGen - MLIRQIRTransformsIncGen - MLIRQTensorTransformsIncGen) +# The TableGen'd pass/conversion headers must be generated before compiling. Each dependency is +# added only if the target is already defined, since the order in which targets become visible +# differs across MLIR toolchains. The PUBLIC link dependencies below additionally enforce the +# correct build order once all targets are resolved. +foreach( + _mqt_capi_dep IN + ITEMS QCToQCOIncGen + QCOToQCIncGen + QCToQIRIncGen + JeffToQCOIncGen + QCOToJeffIncGen + MLIRQCTransformsIncGen + MLIRQCOTransformsIncGen + MLIRQIRTransformsIncGen + MLIRQTensorTransformsIncGen) + if(TARGET ${_mqt_capi_dep}) + add_dependencies(MQTCoreCAPI ${_mqt_capi_dep}) + endif() +endforeach() target_link_libraries( MQTCoreCAPI From 69f962100028c29ad32b7223ccdf2e6ee4a4315f Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:48:08 +0530 Subject: [PATCH 7/9] Adapt mlir c-api to refactored QCtoQIR --- mlir/lib/CAPI/.clang-tidy | 7 +++ mlir/lib/CAPI/CMakeLists.txt | 95 +++++++++++++--------------------- mlir/lib/CAPI/Registration.cpp | 6 ++- 3 files changed, 46 insertions(+), 62 deletions(-) create mode 100644 mlir/lib/CAPI/.clang-tidy diff --git a/mlir/lib/CAPI/.clang-tidy b/mlir/lib/CAPI/.clang-tidy new file mode 100644 index 0000000000..af6b95016c --- /dev/null +++ b/mlir/lib/CAPI/.clang-tidy @@ -0,0 +1,7 @@ +# The C-API registration translation unit includes the dialect, conversion, and +# transformation headers solely to invoke their registration entry points. The +# include-cleaner check cannot trace these uses through the registration +# functions and reports false positives, so it is disabled here. +InheritParentConfig: true +Checks: | + -misc-include-cleaner diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt index 2f57ce59db..6e0466f9b4 100644 --- a/mlir/lib/CAPI/CMakeLists.txt +++ b/mlir/lib/CAPI/CMakeLists.txt @@ -6,66 +6,41 @@ # # Licensed under the MIT License -# The C-API library backs the Python bindings. It is a plain static library (rather than -# add_mlir_public_c_api_library) so that it does not eagerly require its linked MLIR libraries to be -# defined targets at configure time, which is not guaranteed across all supported MLIR toolchains. -add_library(MQTCoreCAPI STATIC Registration.cpp) - -# Export the C-API symbols from the object files. -target_compile_definitions(MQTCoreCAPI PRIVATE MLIR_CAPI_BUILDING_LIBRARY=1) - -# The TableGen'd pass/conversion headers must be generated before compiling. Each dependency is -# added only if the target is already defined, since the order in which targets become visible -# differs across MLIR toolchains. The PUBLIC link dependencies below additionally enforce the -# correct build order once all targets are resolved. -foreach( - _mqt_capi_dep IN - ITEMS QCToQCOIncGen - QCOToQCIncGen - QCToQIRIncGen - JeffToQCOIncGen - QCOToJeffIncGen - MLIRQCTransformsIncGen - MLIRQCOTransformsIncGen - MLIRQIRTransformsIncGen - MLIRQTensorTransformsIncGen) - if(TARGET ${_mqt_capi_dep}) - add_dependencies(MQTCoreCAPI ${_mqt_capi_dep}) - endif() -endforeach() - -target_link_libraries( +add_mlir_public_c_api_library( MQTCoreCAPI - PUBLIC # Dialects - MLIRQCDialect - MLIRQCODialect - MLIRQTensorDialect - MLIRJeff - # Conversions - MLIRQCToQCO - MLIRQCOToQC - MLIRQCToQIR - MLIRJeffToQCO - MLIRQCOToJeff - # Transformations - MLIRQCTransforms - MLIRQCOTransforms - MLIRQIRTransforms - MLIRQTensorTransforms - # OpenQASM 3 import - MLIRQCTranslation - MQT::CoreQASM - MQT::CoreIR - # Upstream - MLIRCAPIIR - MLIRIR - MLIRPass - MLIRTransforms - MLIRArithDialect - MLIRControlFlowDialect - MLIRFuncDialect - MLIRLLVMDialect - MLIRMemRefDialect - MLIRSCFDialect) + Registration.cpp + LINK_LIBS + PUBLIC + # Dialects + MLIRQCDialect + MLIRQCODialect + MLIRQTensorDialect + MLIRJeff + # Conversions + MLIRQCToQCO + MLIRQCOToQC + MLIRQCToQIRBase + MLIRQCToQIRAdaptive + MLIRJeffToQCO + MLIRQCOToJeff + # Transformations + MLIRQCTransforms + MLIRQCOTransforms + MLIRQIRTransforms + MLIRQTensorTransforms + # OpenQASM 3 import + MLIRQCTranslation + MQT::CoreQASM + MQT::CoreIR + # Upstream + MLIRIR + MLIRPass + MLIRTransforms + MLIRArithDialect + MLIRControlFlowDialect + MLIRFuncDialect + MLIRLLVMDialect + MLIRMemRefDialect + MLIRSCFDialect) mqt_mlir_target_use_project_options(MQTCoreCAPI) diff --git a/mlir/lib/CAPI/Registration.cpp b/mlir/lib/CAPI/Registration.cpp index 3e425a78f8..1d6764f609 100644 --- a/mlir/lib/CAPI/Registration.cpp +++ b/mlir/lib/CAPI/Registration.cpp @@ -15,7 +15,8 @@ #include "mlir/Conversion/QCOToJeff/QCOToJeff.h" #include "mlir/Conversion/QCOToQC/QCOToQC.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" -#include "mlir/Conversion/QCToQIR/QCToQIR.h" +#include "mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.h" +#include "mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.h" #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/Transforms/Passes.h" #include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" @@ -63,7 +64,8 @@ void mqtRegisterAllPasses() { // Conversions between the MQT dialects. mlir::registerQCToQCOPasses(); mlir::registerQCOToQCPasses(); - mlir::registerQCToQIRPasses(); + mlir::registerQCToQIRBasePasses(); + mlir::registerQCToQIRAdaptivePasses(); mlir::registerJeffToQCOPasses(); mlir::registerQCOToJeffPasses(); From b4e9f326555578819aecbdb5199037c151e96781 Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sun, 14 Jun 2026 06:11:41 +0530 Subject: [PATCH 8/9] Exclude untestable mlir python bindings from coverage --- .github/codecov.yml | 2 ++ pyproject.toml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index 2f6d5612c9..3f63a6606a 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -2,6 +2,8 @@ ignore: - "**/python" - "test/**/*" - "mlir/unittests/**/*" + # The MLIR Python bindings require an MLIR toolchain built with Python + - "python/mqt/core/mlir/**" coverage: range: 60..90 diff --git a/pyproject.toml b/pyproject.toml index 34e5c12220..6952285369 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -180,6 +180,8 @@ testpaths = ["test/python"] run.source = ["mqt.core"] run.omit = [ '*/_compat/*', + # The MLIR bindings require an MLIR toolchain built with Python bindings. + '*/mqt/core/mlir/*', ] run.disable_warnings = [ "no-sysmon", From 756880ec09ead26da611a58c3ef88d89c2121c4f Mon Sep 17 00:00:00 2001 From: Inderpal Suthar <149688882+InderpalSuthar@users.noreply.github.com> Date: Sun, 14 Jun 2026 06:45:30 +0530 Subject: [PATCH 9/9] Re-trigger CI (flaky macos qdmi timing test)