diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c1a0eee53..e457789ce4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,12 @@ endif() cmake_dependent_option(BUILD_MQT_CORE_QIR_RUNNER "Build the QIR runner of the MQT Core project" ON "BUILD_MQT_CORE_MLIR" OFF) +cmake_dependent_option( + BUILD_MQT_CORE_MLIR_PYTHON + "Build Python bindings for the MQT MLIR compiler collection (requires MLIR_ENABLE_BINDINGS_PYTHON=ON)" + OFF + "BUILD_MQT_CORE_MLIR" + OFF) # add main library code add_subdirectory(src) @@ -143,6 +149,7 @@ endif() if(BUILD_MQT_CORE_MLIR) add_subdirectory(mlir) + add_subdirectory(poc) # copy generated MLIR documentation add_custom_command( diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index cf6726f811..093e094076 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -40,6 +40,17 @@ add_subdirectory(include) add_subdirectory(lib) add_subdirectory(tools) +if(BUILD_MQT_CORE_MLIR_PYTHON) + if(NOT MLIR_ENABLE_BINDINGS_PYTHON) + message(FATAL_ERROR "BUILD_MQT_CORE_MLIR_PYTHON requires MLIR to be built with " + "MLIR_ENABLE_BINDINGS_PYTHON=ON. Re-build MLIR with that flag set.") + endif() + include(AddMLIRPython) + include(MLIRDetectPythonEnv) + mlir_configure_python_dev_packages() + add_subdirectory(python) +endif() + # add test code if(BUILD_MQT_CORE_TESTS) add_subdirectory(unittests) diff --git a/mlir/include/mlir/CAPI/Dialects.h b/mlir/include/mlir/CAPI/Dialects.h new file mode 100644 index 0000000000..74fea37435 --- /dev/null +++ b/mlir/include/mlir/CAPI/Dialects.h @@ -0,0 +1,31 @@ +/* + * 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 + */ + +#pragma once + +#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); + +/** Registers and loads all MQT MLIR dialects into a context. */ +MLIR_CAPI_EXPORTED void mqtMlirRegisterAllDialects(MlirContext context); + +/** Registers all MQT MLIR passes into the global registry. */ +MLIR_CAPI_EXPORTED void mqtMlirRegisterAllPasses(void); + +#ifdef __cplusplus +} +#endif diff --git a/mlir/lib/CAPI/CMakeLists.txt b/mlir/lib/CAPI/CMakeLists.txt new file mode 100644 index 0000000000..6e8307b092 --- /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( + MQTMLIRCoreDialectsCAPI + Dialects.cpp + ADDITIONAL_HEADER_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/CAPI + ENABLE_AGGREGATION + LINK_LIBS + PUBLIC + MLIRIR + MLIRSupport + MLIRQCDialect + MLIRQCODialect + MLIRQTensorDialect + MLIRArithDialect + MLIRFuncDialect + MLIRMemRefDialect + MLIRSCFDialect + MLIRQCTransforms + MLIRQCOTransforms + MLIRQCToQCO) + +mqt_mlir_target_use_project_options(MQTMLIRCoreDialectsCAPI) diff --git a/mlir/lib/CAPI/Dialects.cpp b/mlir/lib/CAPI/Dialects.cpp new file mode 100644 index 0000000000..2840adb761 --- /dev/null +++ b/mlir/lib/CAPI/Dialects.cpp @@ -0,0 +1,46 @@ +/* + * 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 "mlir/CAPI/Dialects.h" + +#include "mlir-c/IR.h" +#include "mlir/CAPI/IR.h" +#include "mlir/CAPI/Registration.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Transforms/Passes.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" // NOLINT(misc-include-cleaner) +#include "mlir/Dialect/QTensor/IR/QTensorDialect.h" + +#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 mqtMlirRegisterAllDialects(MlirContext context) { + mlir::DialectRegistry registry; + registry.insert(); + unwrap(context)->appendDialectRegistry(registry); + unwrap(context)->loadAllAvailableDialects(); +} + +void mqtMlirRegisterAllPasses() { + mlir::qc::registerQCPasses(); + mlir::qco::registerQCOPasses(); + mlir::registerQCToQCOPasses(); +} 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..cd9966f555 --- /dev/null +++ b/mlir/python/CMakeLists.txt @@ -0,0 +1,72 @@ +# 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 + +# Disable versioned soname — causes duplication in Python wheels. +set(CMAKE_PLATFORM_NO_VERSIONED_SONAME ON) + +set(MQT_MLIR_PYTHON_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mqt/core/mlir") +set(MQT_MLIR_PYTHON_PACKAGES_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_packages") + +# ################################################################################################## +# Sources +# ################################################################################################## + +declare_mlir_python_sources(MQTCoreMLIRPythonSources ROOT_DIR "${MQT_MLIR_PYTHON_ROOT_DIR}" SOURCES + __init__.py _pipeline.py) + +declare_mlir_python_sources(MQTCoreMLIRPythonExtensions) + +# ################################################################################################## +# Extension module +# ################################################################################################## + +declare_mlir_python_extension( + MQTCoreMLIRPythonExtensions.Main + MODULE_NAME + _mqtCoreMlir + ADD_TO_PARENT + MQTCoreMLIRPythonExtensions + SOURCES + MQTCoreMLIRModule.cpp + EMBED_CAPI_LINK_LIBS + MQTMLIRCoreDialectsCAPI + MLIRQCTranslation + MLIRSupportMQT + PRIVATE_LINK_LIBS + LLVMSupport + MQT::CoreIR) + +# ################################################################################################## +# Aggregate and install +# ################################################################################################## + +set(_source_components MQTCoreMLIRPythonSources MQTCoreMLIRPythonExtensions) + +add_mlir_python_common_capi_library( + MQTCoreMLIRAggregateCAPI + INSTALL_COMPONENT + MQTCoreMLIRPythonModules + INSTALL_DESTINATION + python_packages/mqt/core/mlir/_mlir_libs + OUTPUT_DIRECTORY + "${MQT_MLIR_PYTHON_PACKAGES_DIR}/mqt/core/mlir/_mlir_libs" + RELATIVE_INSTALL_ROOT + "../../../.." + DECLARED_SOURCES + ${_source_components}) + +add_mlir_python_modules( + MQTCoreMLIRPythonModules + ROOT_PREFIX + "${MQT_MLIR_PYTHON_PACKAGES_DIR}/mqt/core/mlir" + INSTALL_PREFIX + "python_packages/mqt/core/mlir" + DECLARED_SOURCES + ${_source_components} + COMMON_CAPI_LINK_LIBS + MQTCoreMLIRAggregateCAPI) diff --git a/mlir/python/MQTCoreMLIRModule.cpp b/mlir/python/MQTCoreMLIRModule.cpp new file mode 100644 index 0000000000..609a42f031 --- /dev/null +++ b/mlir/python/MQTCoreMLIRModule.cpp @@ -0,0 +1,70 @@ +/* + * 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 "mlir-c/IR.h" +#include "mlir/Bindings/Python/NanobindAdaptors.h" // NOLINT(misc-include-cleaner) +#include "mlir/CAPI/Dialects.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" // NOLINT(misc-include-cleaner) +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" // NOLINT(misc-include-cleaner) +#include "mlir/Support/Passes.h" // NOLINT(misc-include-cleaner) +#include "qasm3/Importer.hpp" + +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) + +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) + +namespace nb = nanobind; // NOLINT(misc-unused-alias-decls) + +// NOLINTNEXTLINE(misc-use-internal-linkage,readability-identifier-naming,readability-named-parameter) +NB_MODULE(_mqtCoreMlir, m) { + mqtMlirRegisterAllPasses(); + + m.doc() = "MQT Core MLIR Python bindings"; + + auto registerDialects = [](MlirContext context) { + mqtMlirRegisterAllDialects(context); + }; + m.def("register_dialects", registerDialects, nb::arg("context"), + "Register and load QC, QCO, QTensor, and dependent MLIR dialects."); + + m.def( + "qasm_to_qco", + [](const std::string& qasm) -> std::string { + auto qc = qasm3::Importer::imports(qasm); + + mlir::MLIRContext ctx; + MlirContext cCtx{&ctx}; + mqtMlirRegisterAllDialects(cCtx); + + auto module = mlir::translateQuantumComputationToQC(&ctx, qc); + if (!module) { + throw std::runtime_error("failed to translate circuit to QC MLIR"); + } + + mlir::PassManager pm(&ctx); + populateQCCleanupPipeline(pm); + pm.addPass(mlir::createQCToQCO()); + if (mlir::failed(pm.run(*module))) { + throw std::runtime_error("qc-to-qco conversion failed"); + } + + std::string out; + llvm::raw_string_ostream os(out); + module->print(os); + return out; + }, + nb::arg("qasm"), + "Run the full (py:qasm) -> (mlir:qc) -> (mlir:qco) pipeline."); +} diff --git a/mlir/python/mqt/core/mlir/__init__.py b/mlir/python/mqt/core/mlir/__init__.py new file mode 100644 index 0000000000..00104c0f05 --- /dev/null +++ b/mlir/python/mqt/core/mlir/__init__.py @@ -0,0 +1,16 @@ +# 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 MLIR compiler collection.""" + +from __future__ import annotations + +from ._mlir_libs._mqtCoreMlir import qasm_to_qco, register_dialects +from ._pipeline import compile_qc_to_qco, make_context + +__all__ = ["compile_qc_to_qco", "make_context", "qasm_to_qco", "register_dialects"] diff --git a/mlir/python/mqt/core/mlir/_pipeline.py b/mlir/python/mqt/core/mlir/_pipeline.py new file mode 100644 index 0000000000..382b93fc06 --- /dev/null +++ b/mlir/python/mqt/core/mlir/_pipeline.py @@ -0,0 +1,39 @@ +# 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 + +"""High-level pipeline helpers built on top of the MLIR Python bindings.""" + +from __future__ import annotations + +from mlir.ir import Context, Module +from mlir.passmanager import PassManager + +from ._mlir_libs._mqtCoreMlir import register_dialects + + +def make_context() -> Context: + """Return an MLIRContext with all MQT dialects registered and loaded.""" + ctx = Context() + register_dialects(ctx) + return ctx + + +def compile_qc_to_qco(mlir_text: str) -> str: + """Run the qc-to-qco conversion pass on an MLIR module given as text. + + Args: + mlir_text: MLIR module in the QC dialect, as a string. + + Returns: + The resulting QCO dialect MLIR module as a string. + """ + with make_context() as ctx: + module = Module.parse(mlir_text, ctx) + pm = PassManager.parse("builtin.module(qc-to-qco)", ctx) + pm.run(module.operation) + return str(module) diff --git a/poc/CMakeLists.txt b/poc/CMakeLists.txt new file mode 100644 index 0000000000..884a0dc9e7 --- /dev/null +++ b/poc/CMakeLists.txt @@ -0,0 +1,27 @@ +# 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_executable(poc-test-pipeline test_pipeline.cpp) + +target_link_libraries( + poc-test-pipeline + PRIVATE MLIRQCDialect + MLIRQCODialect + MLIRQTensorDialect + MLIRArithDialect + MLIRFuncDialect + MLIRMemRefDialect + MLIRSCFDialect + MLIRQCTranslation + MLIRQCToQCO + MLIRSupportMQT + MLIRPass + MQT::CoreIR + MQT::CoreQASM) + +mqt_mlir_target_use_project_options(poc-test-pipeline) diff --git a/poc/test_pipeline.cpp b/poc/test_pipeline.cpp new file mode 100644 index 0000000000..f3d0b5418c --- /dev/null +++ b/poc/test_pipeline.cpp @@ -0,0 +1,84 @@ +/* + * 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 + */ + +/* + * Standalone C++ test for the (py:qasm) -> (mlir:qc) -> (mlir:qco) pipeline. + * + * Build: + * cmake --build build --target poc-test-pipeline + * Run: + * ./build/poc/poc-test-pipeline + */ + +#include "ir/QuantumComputation.hpp" // NOLINT(misc-include-cleaner) +#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 "mlir/Support/Passes.h" +#include "qasm3/Importer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +const char* const BELL_QASM = R"( +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +h q[0]; +cx q[0], q[1]; +)"; +} // namespace + +int main() { + auto qc = qasm3::Importer::imports(BELL_QASM); + + mlir::MLIRContext ctx; + mlir::DialectRegistry registry; + registry.insert(); + ctx.appendDialectRegistry(registry); + ctx.loadAllAvailableDialects(); + + auto module = mlir::translateQuantumComputationToQC(&ctx, qc); + if (!module) { + llvm::errs() << "translation failed\n"; + return 1; + } + + llvm::outs() << "=== mlir:qc ===\n"; + module->print(llvm::outs()); + llvm::outs() << "\n"; + + mlir::PassManager pm(&ctx); + populateQCCleanupPipeline(pm); + pm.addPass(mlir::createQCToQCO()); + if (mlir::failed(pm.run(*module))) { + llvm::errs() << "conversion failed\n"; + return 1; + } + + llvm::outs() << "=== mlir:qco ===\n"; + module->print(llvm::outs()); + llvm::outs() << "\n"; + + return 0; +}