diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7663d27c3..8657662039 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: cmake-args: -DBUILD_MQT_CORE_BENCHMARKS=ON -DBUILD_MQT_CORE_MLIR=ON -DBUILD_MQT_CORE_BINDINGS=ON clang-version: 21 build-project: true - files-changed-only: true + files-changed-only: false setup-python: true install-pkgs: "pybind11==3.0.1" cpp-linter-extra-args: "-std=c++20" diff --git a/.github/workflows/reusable-mlir-tests.yml b/.github/workflows/reusable-mlir-tests.yml index 28e5b7c37c..498ee6987c 100644 --- a/.github/workflows/reusable-mlir-tests.yml +++ b/.github/workflows/reusable-mlir-tests.yml @@ -83,8 +83,8 @@ jobs: - name: Build MLIR lit target run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-lit-test-build-only - - name: Build MLIR translation unittests - run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-translation-test --target mqt-core-mlir-wireiterator-test + - name: Build MLIR unittests + run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-unittests # Test - name: Run lit tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 5852d19761..e2614c6bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264]) ([**@burgholzer**], [**@denialhaag**]) - ✨ Return device handle from `add_dynamic_device_library` for direct backend creation ([#1381]) ([**@marcelwa**]) - ✨ Add IQM JSON support for job submission in Qiskit-QDMI Backend ([#1375], [#1382]) ([**@marcelwa**], [**@burgholzer**]) - ✨ Add authentication support for QDMI sessions with token, username/password, auth file, auth URL, and project ID parameters ([#1355]) ([**@marcelwa**]) @@ -307,6 +308,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1276]: https://github.com/munich-quantum-toolkit/core/pull/1276 [#1271]: https://github.com/munich-quantum-toolkit/core/pull/1271 [#1269]: https://github.com/munich-quantum-toolkit/core/pull/1269 +[#1264]: https://github.com/munich-quantum-toolkit/core/pull/1264 [#1263]: https://github.com/munich-quantum-toolkit/core/pull/1263 [#1247]: https://github.com/munich-quantum-toolkit/core/pull/1247 [#1246]: https://github.com/munich-quantum-toolkit/core/pull/1246 diff --git a/cmake/SetupMLIR.cmake b/cmake/SetupMLIR.cmake index 6ff928c446..096f48d739 100644 --- a/cmake/SetupMLIR.cmake +++ b/cmake/SetupMLIR.cmake @@ -10,7 +10,7 @@ set(MQT_MLIR_SOURCE_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/mlir/include") set(MQT_MLIR_BUILD_INCLUDE_DIR "${PROJECT_BINARY_DIR}/mlir/include") set(MQT_MLIR_MIN_VERSION - "21.0" + "21.1" CACHE STRING "Minimum required MLIR version") # MLIR must be installed on the system diff --git a/docs/mlir/index.md b/docs/mlir/index.md index b409318f93..8184041d5f 100644 --- a/docs/mlir/index.md +++ b/docs/mlir/index.md @@ -121,6 +121,6 @@ module { ## Development -Building the MLIR library requires LLVM version 21.0 or later. +Building the MLIR library requires LLVM version 21.1 or later. Our CI pipeline on GitHub continuously builds and tests the MLIR library on Linux, macOS, and Windows. To access the latest build logs, visit the [GitHub Actions page](https://github.com/munich-quantum-toolkit/core/actions/workflows/ci.yml). diff --git a/mlir/.clang-tidy b/mlir/.clang-tidy new file mode 100644 index 0000000000..9f2230b31f --- /dev/null +++ b/mlir/.clang-tidy @@ -0,0 +1,9 @@ +InheritParentConfig: true +Checks: | + llvm-namespace-comment, + llvm-prefer-isa-or-dyn-cast-in-conditionals, + llvm-prefer-register-over-unsigned, + llvm-prefer-static-over-anonymous-namespace, + -misc-use-anonymous-namespace, + llvm-twine-local, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h new file mode 100644 index 0000000000..8e8ae639d9 --- /dev/null +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +namespace mlir { +class ModuleOp; + +/** + * @brief Configuration for the quantum compiler pipeline + * + * @details + * Controls which stages of the compilation pipeline are executed and + * diagnostic options for profiling and debugging. + */ +struct QuantumCompilerConfig { + /// Convert to QIR at the end of the pipeline + bool convertToQIR = false; + + /// Record intermediate IR at each stage for debugging/testing + bool recordIntermediates = false; + + /// Enable pass timing statistics (MLIR builtin) + bool enableTiming = false; + + /// Enable pass statistics (MLIR builtin) + bool enableStatistics = false; + + /// Print IR after each stage + bool printIRAfterAllStages = false; +}; + +/** + * @brief Records the state of IR at various compilation stages + * + * @details + * Stores string representations of the MLIR module at different + * points in the compilation pipeline. Useful for testing and debugging. + * All stages are recorded when recordIntermediates is enabled. + */ +struct CompilationRecord { + std::string afterQCImport; + std::string afterInitialCanon; + std::string afterQCOConversion; + std::string afterQCOCanon; + std::string afterOptimization; + std::string afterOptimizationCanon; + std::string afterQCConversion; + std::string afterQCCanon; + std::string afterQIRConversion; + std::string afterQIRCanon; +}; + +/** + * @brief Main quantum compiler pipeline + * + * @details + * Provides a high-level interface for compiling quantum programs through + * the MQT compiler infrastructure. The pipeline stages are: + * + * 1. QC dialect (reference semantics) - imported from + * qc::QuantumComputation + * 2. Canonicalization + cleanup + * 3. QCO dialect (value semantics) - enables SSA-based optimizations + * 4. Canonicalization + cleanup + * 5. Quantum optimization passes + * 6. Canonicalization + cleanup + * 7. QC dialect - converted back for backend lowering + * 8. Canonicalization + cleanup + * 9. QIR (Quantum Intermediate Representation) - optional final lowering + * 10. Canonicalization + cleanup + * + * Following MLIR best practices, canonicalization and dead value removal + * are always run after each major transformation stage. + */ +class QuantumCompilerPipeline { +public: + explicit QuantumCompilerPipeline(const QuantumCompilerConfig& config = {}) + : config_(config) {} + + /** + * @brief Run the complete compilation pipeline on a module + * + * @details + * Executes all enabled compilation stages on the provided MLIR module. + * If recordIntermediates is enabled in the config, captures IR snapshots + * at every stage (10 snapshots total for full pipeline). + * + * Automatically configures the PassManager with: + * - Timing statistics if enableTiming is true + * - Pass statistics if enableStatistics is true + * - IR printing after each stage if printIRAfterAllStages is true + * + * @param module The MLIR module to compile + * @param record Optional pointer to record intermediate states + * @return success() if compilation succeeded, failure() otherwise + */ + LogicalResult runPipeline(ModuleOp module, + CompilationRecord* record = nullptr) const; + +private: + /** + * @brief Add canonicalization and cleanup passes + * + * @details + * Always adds the standard MLIR canonicalization pass followed by dead + * value removal. + */ + static void addCleanupPasses(PassManager& pm); + + /** + * @brief Configure PassManager with diagnostic options + * + * @details + * Enables timing, statistics, and IR printing based on config flags. + * Uses MLIR's builtin PassManager configuration methods. + */ + void configurePassManager(PassManager& pm) const; + + QuantumCompilerConfig config_; +}; + +/** + * @brief Utility to capture IR as string + * + * @details + * Prints the MLIR module to a string for recording or comparison. + * + * @param module The module to convert to string + * @return String representation of the IR + */ +std::string captureIR(ModuleOp module); + +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/CMakeLists.txt b/mlir/include/mlir/Conversion/CMakeLists.txt index ba233c542f..f164a5e33d 100644 --- a/mlir/include/mlir/Conversion/CMakeLists.txt +++ b/mlir/include/mlir/Conversion/CMakeLists.txt @@ -10,3 +10,6 @@ add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) +add_subdirectory(QCOToQC) +add_subdirectory(QCToQCO) +add_subdirectory(QCToQIR) diff --git a/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt b/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt new file mode 100644 index 0000000000..7beea05328 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCOToQC.td) +mlir_tablegen(QCOToQC.h.inc -gen-pass-decls -name QCOToQC) +add_public_tablegen_target(QCOToQCIncGen) + +add_mlir_doc(QCOToQC MLIRQCOToQC Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h new file mode 100644 index 0000000000..b2fa86826c --- /dev/null +++ b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +namespace mlir { +#define GEN_PASS_DECL_QCOTOQC +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td new file mode 100644 index 0000000000..1f610d859a --- /dev/null +++ b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def QCOToQC : Pass<"qco-to-qc"> { + let summary = "Convert QCO dialect to QC dialect."; + + let description = [{ + This pass converts all operations from the QCO dialect to their equivalent + operations in the QC dialect. It handles the transformation of qubit + values in QCO to qubit references in QC, ensuring that the semantics + of quantum operations are preserved during the conversion process. + }]; + + // Define dependent dialects + let dependentDialects = [ + "mlir::qco::QCODialect", + "mlir::qc::QCDialect" + ]; +} diff --git a/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt b/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt new file mode 100644 index 0000000000..1cc13df3ff --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCToQCO.td) +mlir_tablegen(QCToQCO.h.inc -gen-pass-decls -name QCToQCO) +add_public_tablegen_target(QCToQCOIncGen) + +add_mlir_doc(QCToQCO MLIRQCToQCO Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h new file mode 100644 index 0000000000..d5d50d7880 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +namespace mlir { +#define GEN_PASS_DECL_QCTOQCO +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td new file mode 100644 index 0000000000..06a225cee1 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def QCToQCO : Pass<"qc-to-qco"> { + let summary = "Convert QC dialect to QCO dialect."; + + let description = [{ + This pass converts all operations from the QC dialect to their equivalent + operations in the QCO dialect. It handles the transformation of qubit + references in QC to qubit values in QCO, ensuring that the semantics + of quantum operations are preserved during the conversion process. + }]; + + // Define dependent dialects + let dependentDialects = [ + "mlir::qc::QCDialect", + "mlir::qco::QCODialect" + ]; +} diff --git a/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt b/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt new file mode 100644 index 0000000000..475145cb25 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCToQIR.td) +mlir_tablegen(QCToQIR.h.inc -gen-pass-decls -name QCToQIR) +add_public_tablegen_target(QCToQIRIncGen) + +add_mlir_doc(QCToQIR MLIRQCToQIR Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h new file mode 100644 index 0000000000..0da943bcc5 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +namespace mlir { +#define GEN_PASS_DECL_QCTOQIR +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td new file mode 100644 index 0000000000..15462eb20d --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def QCToQIR : Pass<"qc-to-qir"> { + let summary = "Lower the QC dialect to the LLVM dialect compliant with QIR 2.0"; + + let description = [{ + This pass lowers all operations from the QC dialect to their equivalent + operations in the LLVM dialect, ensuring compliance with the QIR 2.0 standard. + It translates quantum operations and types from QC to their corresponding + representations in QIR, facilitating interoperability with quantum computing + frameworks that utilize the QIR standard. + + Requirements: + - Input is a valid module in the QC dialect. + - The entry function must be marked with the `entry_point` attribute. + - The input entry function must consist of a single block. + Multi-block input functions are currently not supported. + - The program must have straight-line control flow (i.e., Base Profile QIR). + + Behavior: + - Each QC quantum operation is replaced by a call to the corresponding QIR function in the LLVM dialect. + - Required QIR module flags are attached as attributes to the entry function. + - The pass transforms the single-block entry function into four blocks to satisfy QIR Base Profile constraints: + 0. Initialization block: Sets up the execution environment and performs required runtime initialization. + 1. Reversible operations block: Contains only void-returning calls to reversible quantum operations. + 2. Irreversible operations block: Contains only void-returning calls to operations marked irreversible, e.g.: + `__quantum__qis__mz__body`, `__quantum__qis__reset__body`. + 3. Epilogue block: Records measurement results and returns from the entry function. + - Blocks are connected via unconditional branches in the order listed above. + - Non-quantum dialects are lowered via MLIR's built-in conversions. + + Producing LLVM IR: + - After conversion to the LLVM dialect, produce LLVM IR with: + mlir-translate --mlir-to-llvmir input.mlir > output.ll + }]; + + + // Define dependent dialects + let dependentDialects = [ + "mlir::LLVM::LLVMDialect", + "mlir::QCDialect", + ]; +} diff --git a/mlir/include/mlir/Dialect/CMakeLists.txt b/mlir/include/mlir/Dialect/CMakeLists.txt index 1a85073274..1586a5aead 100644 --- a/mlir/include/mlir/Dialect/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/CMakeLists.txt @@ -8,3 +8,5 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) +add_subdirectory(QC) +add_subdirectory(QCO) diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h new file mode 100644 index 0000000000..2887405614 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qc { + +/** + * @brief Builder API for constructing quantum programs in the QC dialect + * + * @details + * The QCProgramBuilder provides a type-safe interface for constructing + * quantum circuits using reference semantics. Operations modify qubits in + * place without producing new SSA values, providing a natural mapping to + * hardware execution models. + * + * @par Example Usage: + * ```c++ + * QCProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Operations modify qubits in place + * builder.h(q0).cx(q0, q1); + * + * auto module = builder.finalize(); + * ``` + */ +class QCProgramBuilder final : public OpBuilder { +public: + /** + * @brief Construct a new QCProgramBuilder + * @param context The MLIR context to use for building operations + */ + explicit QCProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + /** + * @brief Allocate a single qubit initialized to |0⟩ + * @return A qubit reference + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * ``` + * ```mlir + * %q = qc.alloc : !qc.qubit + * ``` + */ + Value allocQubit(); + + /** + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return A qubit reference + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * ``` + * ```mlir + * %q0 = qc.static 0 : !qc.qubit + * ``` + */ + Value staticQubit(int64_t index); + + /** + * @brief Allocate a qubit register + * @param size Number of qubits (must be positive) + * @param name Register name (default: "q") + * @return Vector of qubit references + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3, "q"); + * ``` + * ```mlir + * %q0 = qc.alloc("q", 3, 0) : !qc.qubit + * %q1 = qc.alloc("q", 3, 1) : !qc.qubit + * %q2 = qc.alloc("q", 3, 2) : !qc.qubit + * ``` + */ + llvm::SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); + + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + std::string registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + std::string name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + if (index < 0 || index >= size) { + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); + } + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + + /** + * @brief Allocate a classical bit register + * @param size Number of bits + * @param name Register name (default: "c") + * @return A ClassicalRegister structure + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(3, "c"); + * ``` + */ + [[nodiscard]] ClassicalRegister + allocClassicalBitRegister(int64_t size, std::string name = "c") const; + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit in the computational basis + * + * @details + * Measures a qubit in place and returns the classical measurement result. + * + * @param qubit The qubit to measure + * @return Classical measurement result (i1) + * + * @par Example: + * ```c++ + * auto result = builder.measure(q); + * ``` + * ```mlir + * %result = qc.measure %q : !qc.qubit -> i1 + * ``` + */ + Value measure(Value qubit); + + /** + * @brief Measure a qubit and store the result in a bit of a register + * + * @param qubit The qubit to measure + * @param bit The classical bit to store the result + * + * @par Example: + * ```c++ + * builder.measure(q0, c[0]); + * ``` + * ```mlir + * %r0 = qc.measure("c", 3, 0) %q0 : !qc.qubit -> i1 + * ``` + */ + QCProgramBuilder& measure(Value qubit, const Bit& bit); + + /** + * @brief Reset a qubit to |0⟩ state + * + * @details + * Resets a qubit to the |0⟩ state in place. + * + * @param qubit The qubit to reset + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.reset(q); + * ``` + * ```mlir + * qc.reset %q : !qc.qubit + * ``` + */ + QCProgramBuilder& reset(Value qubit); + + //===--------------------------------------------------------------------===// + // Unitary Operations + //===--------------------------------------------------------------------===// + + // ZeroTargetOneParameter + +#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM) \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ + /** \ + * Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q) { \ + * qc.OP_NAME(%PARAM) \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); + + DECLARE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME %q : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME %q1 : !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME %q2 : !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) + DECLARE_ONE_TARGET_ZERO_PARAMETER(XOp, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(YOp, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(HOp, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SOp, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TOp, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM) %q1 : !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) %q2 : !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // OneTargetTwoParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q1 : !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q2 : !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, PARAM3, q); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME %q1, %q2 : !qc.qubit, !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME %q2, %q3 : !qc.qubit, !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER + + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) %q2, %q3 : !qc.qubit, !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ + * !qc.qubit \ + * } : !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !qc.qubit, !qc.qubit \ + * } : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + + // BarrierOp + + /** + * @brief Apply a BarrierOp + * + * @param qubits Target qubits + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.barrier({q0, q1}); + * ``` + * ```mlir + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit + * ``` + */ + QCProgramBuilder& barrier(ValueRange qubits); + + //===--------------------------------------------------------------------===// + // Modifiers + //===--------------------------------------------------------------------===// + + /** + * @brief Apply a controlled operation + * + * @param controls Control qubits + * @param body Function that builds the body containing the target operation + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctrl(q0, [&](auto& b) { b.x(q1); }); + * ``` + * ```mlir + * qc.ctrl(%q0) { + * qc.x %q1 : !qc.qubit + * } : !qc.qubit + * ``` + */ + QCProgramBuilder& ctrl(ValueRange controls, + const std::function& body); + + //===--------------------------------------------------------------------===// + // Deallocation + //===--------------------------------------------------------------------===// + + /** + * @brief Explicitly deallocate a qubit + * + * @details + * Deallocates a qubit and removes it from tracking. Optional, finalize() + * automatically deallocates all remaining allocated qubits. + * + * @param qubit The qubit to deallocate + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.dealloc(q); + * ``` + * ```mlir + * qc.dealloc %q : !qc.qubit + * ``` + */ + QCProgramBuilder& dealloc(Value qubit); + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Automatically deallocates all remaining allocated qubits, adds a return + * statement with exit code 0 (indicating successful execution), and + * transfers ownership of the module to the caller. + * The builder should not be used after calling this method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(); + +private: + MLIRContext* ctx{}; + Location loc; + ModuleOp module; + + /// Track allocated qubits for automatic deallocation + llvm::DenseSet allocatedQubits; + + /// Check if the builder has been finalized + void checkFinalized() const; +}; +} // namespace mlir::qc diff --git a/mlir/include/mlir/Dialect/QC/CMakeLists.txt b/mlir/include/mlir/Dialect/QC/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt new file mode 100644 index 0000000000..74da153fac --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect(QCOps qc) +add_mlir_interface(QCInterfaces) +add_mlir_doc(QCOps MLIRQCDialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QCInterfaces MLIRQCInterfaces Dialects/ -gen-op-interface-docs -dialect=qc) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h new file mode 100644 index 0000000000..f07fc0afe0 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +// Suppress warnings about ambiguous reversed operators in MLIR +// (see https://github.com/llvm/llvm-project/issues/45853) +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wambiguous-reversed-operator" +#endif +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DIALECT_NAME_QC "qc" + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QC/IR/QCOpsDialect.h.inc" + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/QC/IR/QCOpsTypes.h.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +namespace mlir::qc { + +/** + * @brief Trait for operations with a fixed number of target qubits and + * parameters + * @details This trait indicates that an operation has a fixed number of target + * qubits and parameters, specified by the template parameters T and P. This is + * helpful for defining operations with known arities, allowing for static + * verification and code generation optimizations. + * @tparam T The target arity. + * @tparam P The parameter arity. + */ +template class TargetAndParameterArityTrait { +public: + template + class Impl : public OpTrait::TraitBase { + public: + static size_t getNumQubits() { return T; } + static size_t getNumTargets() { return T; } + static size_t getNumControls() { return 0; } + + Value getQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } + return this->getOperation()->getOperand(i); + } + Value getTarget(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have targets"); + } + if (i >= T) { + llvm::reportFatalUsageError("Target index out of bounds"); + } + return this->getOperation()->getOperand(i); + } + + static Value getControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + + static size_t getNumParams() { return P; } + + Value getParameter(const size_t i) { + if (i >= P) { + llvm::reportFatalUsageError("Parameter index out of bounds"); + } + return this->getOperation()->getOperand(T + i); + } + + [[nodiscard]] static FloatAttr getStaticParameter(Value param) { + auto constantOp = param.getDefiningOp(); + if (!constantOp) { + return nullptr; + } + return dyn_cast(constantOp.getValue()); + } + }; +}; + +} // namespace mlir::qc + +#include "mlir/Dialect/QC/IR/QCInterfaces.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/QC/IR/QCOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td new file mode 100644 index 0000000000..f63bdadee3 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QC_INTERFACES +#define QC_INTERFACES + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// UnitaryOpInterface +//===----------------------------------------------------------------------===// + +def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { + let description = [{ + This interface provides a unified API for all operations that apply or + produce a unitary transformation. This includes base gates, user-defined + gates, modifier operations (control, inverse, power), and sequences. + + The interface enables uniform introspection and composition capabilities + across all unitary operations in the QC dialect. + }]; + + let cppNamespace = "::mlir::qc"; + + let methods = [ + // Qubit accessors + InterfaceMethod< + "Returns the number of qubits acted on by the unitary operation.", + "size_t", "getNumQubits", (ins) + >, + InterfaceMethod< + "Returns the number of target qubits (excluding control qubits).", + "size_t", "getNumTargets", (ins) + >, + InterfaceMethod< + "Returns the number of control qubits (both positive and negative).", + "size_t", "getNumControls", (ins) + >, + InterfaceMethod< + "Returns the i-th qubit (targets + controls combined).", + "Value", "getQubit", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th target qubit.", + "Value", "getTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th control qubit.", + "Value", "getControl", (ins "size_t":$i) + >, + + // Parameter handling + InterfaceMethod< + "Returns the number of parameters.", + "size_t", "getNumParams", (ins) + >, + InterfaceMethod< + "Returns the i-th parameter.", + "Value", "getParameter", (ins "size_t":$i) + >, + + // Convenience methods + InterfaceMethod< + "Returns true if the operation has any control qubits, otherwise false.", + "bool", "isControlled", (ins), + [{ return getNumControls(impl, tablegen_opaque_val) > 0; }] + >, + InterfaceMethod< + "Returns true if the operation only acts on a single qubit.", + "bool", "isSingleQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] + >, + InterfaceMethod< + "Returns true if the operation acts on two qubits.", + "bool", "isTwoQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] + >, + + // Identification + InterfaceMethod< + "Returns the base symbol/mnemonic of the operation.", + "StringRef", "getBaseSymbol", (ins) + >, + ]; +} + +#endif // QC_INTERFACES diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td new file mode 100644 index 0000000000..7ed127dbfb --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -0,0 +1,976 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QC_OPS +#define QC_OPS + +include "mlir/Dialect/QC/IR/QCInterfaces.td" +include "mlir/IR/BuiltinTypeInterfaces.td" +include "mlir/IR/DialectBase.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpBase.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +//===----------------------------------------------------------------------===// +// QC Dialect Definition +//===----------------------------------------------------------------------===// + +def QCDialect : Dialect { + let name = "qc"; + + let summary = "The QC (reference semantics) dialect for quantum computing."; + + let description = [{ + The QC dialect uses **reference semantics** where quantum operations + modify qubits in place, similar to how hardware physically transforms + quantum states. This model provides: + + - Natural mapping to hardware execution models + - Intuitive representation for circuit descriptions + - Direct compatibility with imperative quantum programming languages + - Straightforward backend code generation + + The name "QC" stands for "Quantum Circuit." + + Example: + ```mlir + qc.h %q // Applies Hadamard to qubit %q in place + qc.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets + ``` + }]; + + let cppNamespace = "::mlir::qc"; + + let useDefaultTypePrinterParser = 1; +} + +//===----------------------------------------------------------------------===// +// QC Type Definitions +//===----------------------------------------------------------------------===// + +class QCType traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def QubitType : QCType<"Qubit", "qubit"> { + let summary = "QC qubit reference type"; + let description = [{ + The `!qc.qubit` type represents a reference to a quantum bit in the + QC dialect. Operations using this type modify qubits in place using + reference semantics, similar to how classical imperative languages handle + mutable references. + }]; +} + +//===----------------------------------------------------------------------===// +// Base Operation Classes +//===----------------------------------------------------------------------===// + +class QCOp traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Resource Operations +//===----------------------------------------------------------------------===// + +def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { + let summary = "Allocate a qubit dynamically"; + let description = [{ + Allocates a new qubit dynamically and returns a reference to it. + The qubit is initialized to the |0⟩ state. + + Optionally, the qubit can be part of a register by specifying: + - `register_name`: The name of the register this qubit belongs to + - `register_size`: The total size of the register + - `register_index`: The index of this qubit within the register + + Example (single qubit): + ```mlir + %q = qc.alloc : !qc.qubit + ``` + + Example (qubits in a register): + ```mlir + %q0 = qc.alloc("q", 3, 0) : !qc.qubit + %q1 = qc.alloc("q", 3, 1) : !qc.qubit + %q2 = qc.alloc("q", 3, 2) : !qc.qubit + ``` + }]; + + let arguments = (ins OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); + let results = (outs QubitType:$result); + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + attr-dict `:` type($result) + }]; + + let builders = [ + OpBuilder<(ins), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + }]>, + OpBuilder<(ins "::mlir::StringAttr":$register_name, + "::mlir::IntegerAttr":$register_size, + "::mlir::IntegerAttr":$register_index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), + register_name, register_size, register_index); + }]> + ]; + + let hasVerifier = 1; +} + +def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { + let summary = "Deallocate a qubit"; + let description = [{ + Deallocates a qubit, releasing its resources. + + Example: + ```mlir + qc.dealloc %q : !qc.qubit + ``` + }]; + + let arguments = (ins QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; + + let hasCanonicalizer = 1; +} + +def StaticOp : QCOp<"static", [Pure]> { + let summary = "Retrieve a static qubit by index"; + let description = [{ + The `qc.static` operation produces an SSA value representing a qubit + identified by a static index. This is useful for referring to fixed + qubits in a quantum program or to hardware-mapped qubits. + + Example: + ```mlir + %q = qc.static 0 : !qc.qubit + ``` + }]; + + let arguments = (ins ConfinedAttr:$index); + let results = (outs QubitType:$qubit); + let assemblyFormat = "$index attr-dict `:` type($qubit)"; +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset Operations +//===----------------------------------------------------------------------===// + +def MeasureOp : QCOp<"measure"> { + let summary = "Measure a qubit in the computational basis"; + let description = [{ + Measures a qubit in the computational (Z) basis, collapsing the state + and returning a classical bit result. + + Optionally, the measurement can be recorded to an output register by + specifying: + - `register_name`: Name of the classical register (e.g., "c") + - `register_size`: Total size of the register + - `register_index`: Index within the register for this measurement + + Example (simple measurement): + ```mlir + %result = qc.measure %q : !qc.qubit -> i1 + ``` + + Example (measurement with output recording): + ```mlir + %result = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 + ``` + }]; + + let arguments = (ins Arg:$qubit, + OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); + let results = (outs I1:$result); + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + $qubit `:` type($qubit) `->` type($result) attr-dict + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit), [{ + build($_builder, $_state, $_builder.getI1Type(), qubit, nullptr, nullptr, nullptr); + }]> + ]; + + let hasVerifier = 1; +} + +def ResetOp : QCOp<"reset"> { + let summary = "Reset a qubit to |0⟩ state"; + let description = [{ + Resets a qubit to the |0⟩ state, regardless of its current state. + + Example: + ```mlir + qc.reset %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; +} + +//===----------------------------------------------------------------------===// +// Traits +//===----------------------------------------------------------------------===// + +class TargetAndParameterArityTrait + : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { + let cppNamespace = "::mlir::qc"; +} + +def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; +def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; +def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; +def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; +def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; +def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; +def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +def GPhaseOp : QCOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { + let summary = "Apply a global phase to the state"; + let description = [{ + Applies a global phase to the state. + + Example: + ```mlir + qc.gphase(%theta) + ``` + }]; + + let arguments = (ins Arg:$theta); + let assemblyFormat = "`(` $theta `)` attr-dict"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "gphase"; } + }]; + + let builders = [ + OpBuilder<(ins "const std::variant&":$theta)> + ]; +} + +def IdOp : QCOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Id gate to a qubit"; + let description = [{ + Applies an Id gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.id %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "id"; } + }]; +} + +def XOp : QCOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an X gate to a qubit"; + let description = [{ + Applies an X gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.x %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "x"; } + }]; +} + +def YOp : QCOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Y gate to a qubit"; + let description = [{ + Applies a Y gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.y %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "y"; } + }]; +} + +def ZOp : QCOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Z gate to a qubit"; + let description = [{ + Applies a Z gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.z %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "z"; } + }]; +} + +def HOp : QCOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an H gate to a qubit"; + let description = [{ + Applies an H gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.h %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "h"; } + }]; +} + +def SOp : QCOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an S gate to a qubit"; + let description = [{ + Applies an S gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.s %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "s"; } + }]; +} + +def SdgOp : QCOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Sdg gate to a qubit"; + let description = [{ + Applies an Sdg gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.sdg %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sdg"; } + }]; +} + +def TOp : QCOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a T gate to a qubit"; + let description = [{ + Applies a T gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.t %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "t"; } + }]; +} + +def TdgOp : QCOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Tdg gate to a qubit"; + let description = [{ + Applies a Tdg gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.tdg %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "tdg"; } + }]; +} + +def SXOp : QCOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SX gate to a qubit"; + let description = [{ + Applies an SX gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.sx %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sx"; } + }]; +} + +def SXdgOp : QCOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SXdg gate to a qubit"; + let description = [{ + Applies an SXdg gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.sxdg %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sxdg"; } + }]; +} + +def RXOp : QCOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RX gate to a qubit"; + let description = [{ + Applies an RX gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.rx(%theta) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + +def RYOp : QCOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RY gate to a qubit"; + let description = [{ + Applies an RY gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.ry(%theta) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ry"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + +def RZOp : QCOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RZ gate to a qubit"; + let description = [{ + Applies an RZ gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.rz(%theta) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + +def POp : QCOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply a P gate to a qubit"; + let description = [{ + Applies a P gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.p(%theta) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "p"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + +def ROp : QCOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply an R gate to a qubit"; + let description = [{ + Applies an R gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.r(%theta, %phi) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi); + let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "r"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi)> + ]; +} + +def U2Op : QCOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply a U2 gate to a qubit"; + let description = [{ + Applies a U2 gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.u2(%phi, %lambda) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$phi, + Arg:$lambda); + let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "u2"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; +} + +def UOp : QCOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { + let summary = "Apply a U gate to a qubit"; + let description = [{ + Applies a U gate to a qubit, modifying it in place. + + Example: + ```mlir + qc.u(%theta, %phi, %lambda) %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi, + Arg:$lambda); + let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "u"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; +} + +def SWAPOp : QCOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a SWAP gate to two qubits"; + let description = [{ + Applies a SWAP gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.swap %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "swap"; } + }]; +} + +def iSWAPOp : QCOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a iSWAP gate to two qubits"; + let description = [{ + Applies a iSWAP gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.iswap %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "iswap"; } + }]; +} + +def DCXOp : QCOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a DCX gate to two qubits"; + let description = [{ + Applies a DCX gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.dcx %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "dcx"; } + }]; +} + +def ECROp : QCOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply an ECR gate to two qubits"; + let description = [{ + Applies an ECR gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.ecr %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ecr"; } + }]; +} + +def RXXOp : QCOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RXX gate to two qubits"; + let description = [{ + Applies an RXX gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.rxx(%theta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rxx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RYYOp : QCOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RYY gate to two qubits"; + let description = [{ + Applies an RYY gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.ryy(%theta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ryy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZXOp : QCOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZX gate to two qubits"; + let description = [{ + Applies an RZX gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.rzx(%theta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rzx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZZOp : QCOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZZ gate to two qubits"; + let description = [{ + Applies an RZZ gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.rzz(%theta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rzz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def XXPlusYYOp : QCOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX+YY gate to two qubits"; + let description = [{ + Applies an XX+YY gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.xx_plus_yy(%theta, %beta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "xx_plus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + +def XXMinusYYOp : QCOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX-YY gate to two qubits"; + let description = [{ + Applies an XX-YY gate to two qubits, modifying them in place. + + Example: + ```mlir + qc.xx_minus_yy(%theta, %beta) %q0, %q1 : !qc.qubit, !qc.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "xx_minus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + +def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { + let summary = "Apply a barrier gate to a set of qubits"; + let description = [{ + Applies a barrier gate to a set of qubits, modifying them in place. + + Example: + ```mlir + qc.barrier %q : !qc.qubit + ``` + }]; + + let arguments = (ins Arg, "the target qubits", [MemRead, MemWrite]>:$qubits); + let assemblyFormat = "$qubits attr-dict `:` type($qubits)"; + + let extraClassDeclaration = [{ + size_t getNumQubits(); + size_t getNumTargets(); + static size_t getNumControls(); + Value getQubit(size_t i); + Value getTarget(size_t i); + static Value getControl(size_t i); + static size_t getNumParams(); + static Value getParameter(size_t i); + static StringRef getBaseSymbol() { return "barrier"; } + }]; +} + +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +def YieldOp : QCOp<"yield", traits = [Terminator]> { + let summary = "Yield from a modifier region"; + let description = [{ + Terminates a modifier region, yielding control back to the enclosing operation. + }]; + + let assemblyFormat = "attr-dict"; +} + +def CtrlOp : QCOp<"ctrl", + traits = [ + UnitaryOpInterface, + SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">, + RecursiveMemoryEffects + ]> { + let summary = "Add control qubits to a unitary operation"; + let description = [{ + A modifier operation that adds control qubits to the unitary operation + defined in its body region. The controlled operation applies the + underlying unitary only when all control qubits are in the |1⟩ state. + + Note that control qubits are logically unmodified by this operation in that + their quantum state remains unchanged. However, the `controls` argument + is marked with `MemWrite` to ensure correct dependency tracking in MLIR. + + Example: + ```mlir + qc.ctrl(%q0) { + qc.x %q1 : !qc.qubit + } + ``` + }]; + + let arguments = (ins Arg, "the control qubits", [MemRead, MemWrite]>:$controls); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "`(` $controls `)` $body attr-dict `:` type($controls)"; + + let extraClassDeclaration = [{ + [[nodiscard]] UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + Value getQubit(size_t i); + Value getTarget(size_t i); + Value getControl(size_t i); + size_t getNumParams(); + Value getParameter(size_t i); + static StringRef getBaseSymbol() { return "ctrl"; } + }]; + + let builders = [ + OpBuilder<(ins "ValueRange":$controls, "UnitaryOpInterface":$bodyUnitary)>, + OpBuilder<(ins "ValueRange":$controls, "const std::function&":$bodyBuilder)> + ]; + + let hasCanonicalizer = 1; + let hasVerifier = 1; +} + +#endif // QC_OPS diff --git a/mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h new file mode 100644 index 0000000000..ed0c850c51 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include + +namespace qc { +class QuantumComputation; +} + +namespace mlir { + +OwningOpRef +translateQuantumComputationToQC(MLIRContext* context, + const ::qc::QuantumComputation& qc); +} diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h new file mode 100644 index 0000000000..cd5f4ae897 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -0,0 +1,1079 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { + +/** + * @brief Builder API for constructing quantum programs in the QCO dialect + * + * @details + * The QCOProgramBuilder provides a type-safe interface for constructing + * quantum circuits using value semantics. Operations consume input qubit + * SSA values and produce new output values, following the functional + * programming paradigm. + * + * @par Linear Type Enforcement: + * The builder enforces linear type semantics by tracking valid qubit SSA + * values. Once a qubit is consumed by an operation producing a new version + * (e.g., reset, measure), the old SSA value is invalidated. This prevents + * use-after-consume errors and mirrors quantum computing's no-cloning theorem. + * + * @par Example Usage: + * ```c++ + * QCOProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Operations return updated values + * q0 = builder.h(q0); + * std::tie(q0, q1) = builder.cx(q0, q1); + * + * auto module = builder.finalize(); + * ``` + */ +class QCOProgramBuilder final : public OpBuilder { +public: + /** + * @brief Construct a new QCOProgramBuilder + * @param context The MLIR context to use for building operations + */ + explicit QCOProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + /** + * @brief Allocate a single qubit initialized to |0⟩ + * @return A tracked, valid qubit SSA value + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * ``` + * ```mlir + * %q = qco.alloc : !qco.qubit + * ``` + */ + Value allocQubit(); + + /** + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return A tracked, valid qubit SSA value + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * ``` + * ```mlir + * %q0 = qco.static 0 : !qco.qubit + * ``` + */ + Value staticQubit(int64_t index); + + /** + * @brief Allocate a qubit register + * @param size Number of qubits (must be positive) + * @param name Register name (default: "q") + * @return Vector of tracked, valid qubit SSA values + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3, "q"); + * ``` + * ```mlir + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit + * %q1 = qco.alloc("q", 3, 1) : !qco.qubit + * %q2 = qco.alloc("q", 3, 2) : !qco.qubit + * ``` + */ + llvm::SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); + + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + std::string registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + std::string name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + if (index < 0 || index >= size) { + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); + } + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + + /** + * @brief Allocate a classical bit register + * @param size Number of bits + * @param name Register name (default: "c") + * @return A ClassicalRegister structure + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(3, "c"); + * ``` + */ + [[nodiscard]] ClassicalRegister + allocClassicalBitRegister(int64_t size, std::string name = "c") const; + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit in the computational basis + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value + * along with the measurement result (i1). The input is validated and + * tracking is updated to reflect the new output value. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Pair of (output_qubit, measurement_result) + * + * @par Example: + * ```c++ + * auto [q_out, result] = builder.measure(q); + * ``` + * ```mlir + * %q_out, %result = qco.measure %q : !qco.qubit + * ``` + */ + std::pair measure(Value qubit); + + /** + * @brief Measure a qubit and record the result in a bit of a register + * + * @param qubit Input qubit (must be valid/unconsumed) + * @param bit The classical bit to record the result + * @return Output qubit value + * + * @par Example: + * ```c++ + * q0 = builder.measure(q0, c[0]); + * ``` + * ```mlir + * %q0_out, %r0 = qco.measure("c", 3, 0) %q0 : !qco.qubit + * ``` + */ + Value measure(Value qubit, const Bit& bit); + + /** + * @brief Reset a qubit to |0⟩ state + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value + * in the |0⟩ state. The input is validated and tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * q = builder.reset(q); + * ``` + * ```mlir + * %q_out = qco.reset %q : !qco.qubit -> !qco.qubit + * ``` + */ + Value reset(Value qubit); + + //===--------------------------------------------------------------------===// + // Unitary Operations + //===--------------------------------------------------------------------===// + + // ZeroTargetOneParameter + +#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM); \ + * ``` \ + * ```mlir \ + * qco.OP_NAME(%PARAM) \ + * ``` \ + */ \ + void OP_NAME(const std::variant&(PARAM)); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit \ + * @return Output control qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.c##OP_NAME(PARAM, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = qco.ctrl(%q_in) { \ + * qco.OP_NAME(%PARAM) \ + * qco.yield \ + * } : ({!qco.qubit}) -> ({!qco.qubit}) \ + * ``` \ + */ \ + Value c##OP_NAME(const std::variant&(PARAM), Value control); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @return Output control qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.ctrl(%q0_in, %q1_in) { \ + * qco.OP_NAME(%PARAM) \ + * qco.yield \ + * } : ({!qco.qubit, !qco.qubit}) -> ({!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + ValueRange mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); + + DECLARE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(q_in); \ + * ``` \ + * ```mlir \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + */ \ + Value OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME %q1_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME({q0_in, q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME %q2_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) + DECLARE_ONE_TARGET_ZERO_PARAMETER(XOp, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(YOp, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(HOp, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SOp, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TOp, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant&(PARAM), Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM) %q1_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}, \ + * q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM) %q2_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // OneTargetTwoParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM1, PARAM2, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM1, %PARAM2) %q1_in : !qco.qubit -> \ + * !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, {q0_in, \ + * q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM1, %PARAM2) %q2_in : !qco.qubit -> \ + * !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM1, PARAM2, PARAM3, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit -> \ + * !qco.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0_in, \ + * q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1_in : !qco.qubit \ + * -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, \ + * {q0_in, q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2_in : !qco.qubit \ + * -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(q0_in, q1_in, q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME %q1_in, %q2_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, \ + * {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME({q0_in, q1_in}, \ + * q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = qco.OP_NAME %q2_in, %q3_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + ValueRange controls, Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER + + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(PARAM, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM, q0_in, q1_in, \ + * q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME(%PARAM) %q1_in, %q2_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, \ + * {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value qubit0, \ + Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM, {q0_in, \ + * q1_in}, q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = qco.OP_NAME(%PARAM) %q2_in, %q3_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, \ + * q1_in, q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME(%PARAM1, %PARAM2) %q1_in, %q2_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit}, {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM1, PARAM2, \ + * {q0_in, q1_in}, q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = qco.OP_NAME(%PARAM1, %PARAM2) %q2_in, %q3_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + + // BarrierOp + + /** + * @brief Apply a BarrierOp + * + * @param qubits Input qubits (must be valid/unconsumed) + * @return Output qubits + * + * @par Example: + * ```c++ + * builder.barrier({q0, q1}); + * ``` + * ```mlir + * qco.barrier %q0, %q1 : !qco.qubit, !qco.qubit -> !qco.qubit, + * !qco.qubit + * ``` + */ + ValueRange barrier(ValueRange qubits); + + //===--------------------------------------------------------------------===// + // Modifiers + //===--------------------------------------------------------------------===// + + /** + * @brief Apply a controlled operation + * + * @param controls Control qubits + * @param targets Target qubits + * @param body Function that builds the body containing the target operation + * @return Pair of (output_control_qubits, output_target_qubits) + * + * @par Example: + * ```c++ + * {controls_out, targets_out} = builder.ctrl(q0_in, q1_in, [&](auto& b) { + * auto q1_res = b.x(q1_in); + * return {q1_res}; + * }); + * ``` + * ```mlir + * %controls_out, %targets_out = qco.ctrl(%q0_in) %q1_in { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + * ``` + */ + std::pair + ctrl(ValueRange controls, ValueRange targets, + const std::function& body); + + //===--------------------------------------------------------------------===// + // Deallocation + //===--------------------------------------------------------------------===// + + /** + * @brief Explicitly deallocate a qubit + * + * @details + * Validates and removes the qubit from tracking. Optional, finalize() + * automatically deallocates all remaining qubits. + * + * @param qubit Qubit to deallocate (must be valid/unconsumed) + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.dealloc(q); + * ``` + * ```mlir + * qco.dealloc %q : !qco.qubit + * ``` + */ + QCOProgramBuilder& dealloc(Value qubit); + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Automatically deallocates all remaining valid qubits, adds a return + * statement with exit code 0 (indicating successful execution), and + * transfers ownership of the module to the caller. + * The builder should not be used after calling this method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(); + +private: + MLIRContext* ctx{}; + Location loc; + ModuleOp module; + + /// Check if the builder has been finalized + void checkFinalized() const; + + //===--------------------------------------------------------------------===// + // Linear Type Tracking Helpers + //===--------------------------------------------------------------------===// + + /** + * @brief Validate that a qubit value is valid and unconsumed + * @param qubit Qubit value to validate + * @throws Aborts if qubit is not tracked (consumed or never created) + */ + void validateQubitValue(Value qubit) const; + + /** + * @brief Update tracking when an operation consumes and produces a qubit + * @param inputQubit Input qubit being consumed (must be valid) + * @param outputQubit New output qubit being produced + */ + void updateQubitTracking(Value inputQubit, Value outputQubit); + + /// Track valid (unconsumed) qubit SSA values for linear type enforcement. + /// Only values present in this set are valid for use in operations. + /// When an operation consumes a qubit and produces a new one, the old value + /// is removed and the new output is added. + llvm::DenseSet validQubits; +}; +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/CMakeLists.txt b/mlir/include/mlir/Dialect/QCO/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt new file mode 100644 index 0000000000..bddaba3d7a --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect(QCOOps qco) +add_mlir_interface(QCOInterfaces) +add_mlir_doc(QCOOps MLIRQCODialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QCOInterfaces MLIRQCOInterfaces Dialects/ -gen-op-interface-docs -dialect=qco) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h new file mode 100644 index 0000000000..d1087ab6f7 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +// Suppress warnings about ambiguous reversed operators in MLIR +// (see https://github.com/llvm/llvm-project/issues/45853) +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wambiguous-reversed-operator" +#endif +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DIALECT_NAME_QCO "qco" + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QCO/IR/QCOOpsDialect.h.inc" + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.h.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +namespace mlir::qco { + +/** + * @brief Trait for operations with a fixed number of target qubits and + * parameters + * @details This trait indicates that an operation has a fixed number of target + * qubits and parameters, specified by the template parameters T and P. This is + * helpful for defining operations with known arities, allowing for static + * verification and code generation optimizations. + * @tparam T The target arity. + * @tparam P The parameter arity. + */ +template class TargetAndParameterArityTrait { +public: + template + class Impl : public OpTrait::TraitBase { + public: + static size_t getNumQubits() { return T; } + static size_t getNumTargets() { return T; } + static size_t getNumControls() { return 0; } + + Value getInputQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } + return this->getOperation()->getOperand(i); + } + Value getOutputQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } + return this->getOperation()->getResult(i); + } + + Value getInputTarget(const size_t i) { return getInputQubit(i); } + Value getOutputTarget(const size_t i) { return getOutputQubit(i); } + + static Value getInputControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + static Value getOutputControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + + static size_t getNumParams() { return P; } + + Value getParameter(const size_t i) { + if (i >= P) { + llvm::reportFatalUsageError("Parameter index out of bounds"); + } + return this->getOperation()->getOperand(T + i); + } + + [[nodiscard]] static FloatAttr getStaticParameter(Value param) { + auto constantOp = param.getDefiningOp(); + if (!constantOp) { + return nullptr; + } + return dyn_cast(constantOp.getValue()); + } + + Value getInputForOutput(Value output) { + const auto& op = this->getOperation(); + for (size_t i = 0; i < T; ++i) { + if (output == op->getResult(i)) { + return op->getOperand(i); + } + } + llvm::reportFatalUsageError( + "Given qubit is not an output of the operation"); + } + Value getOutputForInput(Value input) { + const auto& op = this->getOperation(); + for (size_t i = 0; i < T; ++i) { + if (input == op->getOperand(i)) { + return op->getResult(i); + } + } + llvm::reportFatalUsageError( + "Given qubit is not an input of the operation"); + } + }; +}; + +} // namespace mlir::qco + +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/QCO/IR/QCOOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td new file mode 100644 index 0000000000..8e71a52a55 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td @@ -0,0 +1,113 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QCO_INTERFACES +#define QCO_INTERFACES + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// UnitaryOpInterface +//===----------------------------------------------------------------------===// + +def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { + let description = [{ + This interface provides a unified API for all operations that apply or + produce a unitary transformation in the QCO dialect. This includes base + gates, user-defined gates, modifier operations (control, inverse, power), + and sequences. + + The interface enables uniform introspection and composition capabilities + across all unitary operations with value semantics. + }]; + + let cppNamespace = "::mlir::qco"; + + let methods = [ + // Qubit accessors + InterfaceMethod< + "Returns the number of qubits acted on by the unitary operation.", + "size_t", "getNumQubits", (ins) + >, + InterfaceMethod< + "Returns the number of target qubits (excluding control qubits).", + "size_t", "getNumTargets", (ins) + >, + InterfaceMethod< + "Returns the number of control qubits (both positive and negative).", + "size_t", "getNumControls", (ins) + >, + InterfaceMethod< + "Returns the i-th input qubit (targets + controls combined).", + "Value", "getInputQubit", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th output qubit (targets + controls combined).", + "Value", "getOutputQubit", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th target input qubit.", + "Value", "getInputTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th target output qubit.", + "Value", "getOutputTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th control input qubit.", + "Value", "getInputControl", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th control output qubit.", + "Value", "getOutputControl", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the input qubit corresponding to the given output qubit.", + "Value", "getInputForOutput", (ins "Value":$output) + >, + InterfaceMethod< + "Returns the output qubit corresponding to the given input qubit.", + "Value", "getOutputForInput", (ins "Value":$input) + >, + + // Parameter handling + InterfaceMethod< + "Returns the number of parameters.", + "size_t", "getNumParams", (ins) + >, + InterfaceMethod< + "Returns the i-th parameter.", + "Value", "getParameter", (ins "size_t":$i) + >, + + // Convenience methods + InterfaceMethod< + "Returns true if the operation has any control qubits, otherwise false.", + "bool", "isControlled", (ins), + [{ return getNumControls(impl, tablegen_opaque_val) > 0; }] + >, + InterfaceMethod< + "Returns true if the operation only acts on a single qubit.", + "bool", "isSingleQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] + >, + InterfaceMethod< + "Returns true if the operation acts on two qubits.", + "bool", "isTwoQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] + >, + + // Identification + InterfaceMethod< + "Returns the base symbol/mnemonic of the operation.", + "StringRef", "getBaseSymbol", (ins) + >, + ]; +} + +#endif // QCO_INTERFACES diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td new file mode 100644 index 0000000000..69f89eb5e0 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -0,0 +1,1109 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QCOOPS +#define QCOOPS + +include "mlir/Dialect/QCO/IR/QCOInterfaces.td" +include "mlir/IR/BuiltinTypeInterfaces.td" +include "mlir/IR/DialectBase.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpBase.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +def QCODialect : Dialect { + let name = "qco"; + + let summary = "The QCO (value semantics) dialect for quantum computing."; + + let description = [{ + The QCO dialect uses **value semantics** where quantum operations + consume input qubits and produce new output values, following the + functional programming and SSA paradigm. This model enables: + + - Powerful compiler optimizations through clear dataflow + - Safe reordering and parallelization analysis + - Advanced transformation passes + - Explicit dependency tracking + + The name "QCO" stands for "Quantum Circuit Optimization." + + Example: + ```mlir + %q_out = qco.h %q_in // Consumes %q_in, produces %q_out + %q0_out, %q1_out = qco.swap %q0_in, %q1_in // Consumes inputs, produces outputs + ``` + }]; + + let cppNamespace = "::mlir::qco"; + + let useDefaultTypePrinterParser = 1; +} + +//===----------------------------------------------------------------------===// +// QCO Type Definitions +//===----------------------------------------------------------------------===// + +class QCOType traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def QubitType : QCOType<"Qubit", "qubit"> { + let summary = "QCO qubit value type"; + let description = [{ + The `!qco.qubit` type represents an SSA value holding a quantum bit + in the QCO dialect. Operations using this type consume input qubits + and produce new output qubits following value semantics and the SSA + paradigm, enabling powerful dataflow analysis and optimization. + + Example: + ```mlir + %q0 = qco.alloc : !qco.qubit + %q1 = qco.h %q0 : !qco.qubit -> !qco.qubit + %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit + ``` + }]; +} + +//===----------------------------------------------------------------------===// +// Base Operation Classes +//===----------------------------------------------------------------------===// + +class QCOOp traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Resource Operations +//===----------------------------------------------------------------------===// + +def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { + let summary = "Allocate a qubit dynamically"; + let description = [{ + Allocates a new qubit dynamically and returns an SSA value representing it. + The qubit is initialized to the |0⟩ state. + + Optionally, the qubit can be part of a register by specifying: + - `register_name`: The name of the register this qubit belongs to + - `register_size`: The total size of the register + - `register_index`: The index of this qubit within the register + + Example (single qubit): + ```mlir + %q = qco.alloc : !qco.qubit + ``` + + Example (qubits in a register): + ```mlir + %q0 = qco.alloc("q", 3, 0) : !qco.qubit + %q1 = qco.alloc("q", 3, 1) : !qco.qubit + %q2 = qco.alloc("q", 3, 2) : !qco.qubit + ``` + }]; + + let arguments = (ins OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); + let results = (outs QubitType:$result); + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + attr-dict `:` type($result) + }]; + + let builders = [ + OpBuilder<(ins), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + }]>, + OpBuilder<(ins "::mlir::StringAttr":$register_name, + "::mlir::IntegerAttr":$register_size, + "::mlir::IntegerAttr":$register_index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), + register_name, register_size, register_index); + }]> + ]; + + let hasVerifier = 1; +} + +def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { + let summary = "Deallocate a qubit"; + let description = [{ + Deallocates a qubit, releasing its resources. + + Example: + ```mlir + qco.dealloc %q : !qco.qubit + ``` + }]; + + let arguments = (ins QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; + let hasCanonicalizer = 1; +} + +def StaticOp : QCOOp<"static", [Pure]> { + let summary = "Retrieve a static qubit by index"; + let description = [{ + The `qco.static` operation produces an SSA value representing a qubit + identified by a static index. This is useful for referring to fixed + qubits in a quantum program or to hardware-mapped qubits. + + Example: + ```mlir + %q = qco.static 0 : !qco.qubit + ``` + }]; + + let arguments = (ins ConfinedAttr:$index); + let results = (outs QubitType:$qubit); + let assemblyFormat = "$index attr-dict `:` type($qubit)"; +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset Operations +//===----------------------------------------------------------------------===// + +def MeasureOp : QCOOp<"measure"> { + let summary = "Measure a qubit in the computational basis"; + let description = [{ + Measures a qubit in the computational (Z) basis, collapsing the state + and returning both the output qubit and a classical bit result. + + Optionally, the measurement can be recorded to an output register by + specifying: + - `register_name`: Name of the classical register (e.g., "c") + - `register_size`: Total size of the register + - `register_index`: Index within the register for this measurement + + Example (simple measurement): + ```mlir + %q_out, %result = qco.measure %q_in : !qco.qubit + ``` + + Example (measurement with output recording): + ```mlir + %q_out, %result = qco.measure("c", 2, 0) %q_in : !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); + let results = (outs QubitType:$qubit_out, I1:$result); + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + $qubit_in `:` type($qubit_in) attr-dict + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), $_builder.getI1Type(), + qubit_in, nullptr, nullptr, nullptr); + }]> + ]; + + let hasVerifier = 1; +} + +def ResetOp : QCOOp<"reset", [Idempotent, SameOperandsAndResultType]> { + let summary = "Reset a qubit to |0⟩ state"; + let description = [{ + Resets a qubit to the |0⟩ state, regardless of its current state, + and returns the reset qubit. + + Example: + ```mlir + %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let hasCanonicalizer = 1; +} + +//===----------------------------------------------------------------------===// +// Traits +//===----------------------------------------------------------------------===// + +class TargetAndParameterArityTrait + : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { + let cppNamespace = "::mlir::qco"; +} + +def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; +def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; +def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; +def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; +def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; +def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; +def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +def GPhaseOp : QCOOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { + let summary = "Apply a global phase to the state"; + let description = [{ + Applies a global phase to the state. + + Example: + ```mlir + qco.gphase(%theta) + ``` + }]; + + let arguments = (ins Arg:$theta); + let assemblyFormat = "`(` $theta `)` attr-dict"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "gphase"; } + }]; + + let builders = [ + OpBuilder<(ins "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Id gate to a qubit"; + let description = [{ + Applies an Id gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.id %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "id"; } + }]; + + let hasCanonicalizer = 1; +} + +def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an X gate to a qubit"; + let description = [{ + Applies an X gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.x %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "x"; } + }]; + + let hasCanonicalizer = 1; +} + +def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Y gate to a qubit"; + let description = [{ + Applies a Y gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.y %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "y"; } + }]; + + let hasCanonicalizer = 1; +} + +def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Z gate to a qubit"; + let description = [{ + Applies a Z gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.z %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "z"; } + }]; + + let hasCanonicalizer = 1; +} + +def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a H gate to a qubit"; + let description = [{ + Applies a H gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.h %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "h"; } + }]; + + let hasCanonicalizer = 1; +} + +def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an S gate to a qubit"; + let description = [{ + Applies an S gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.s %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "s"; } + }]; + + let hasCanonicalizer = 1; +} + +def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Sdg gate to a qubit"; + let description = [{ + Applies an Sdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.sdg %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sdg"; } + }]; + + let hasCanonicalizer = 1; +} + +def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a T gate to a qubit"; + let description = [{ + Applies a T gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.t %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "t"; } + }]; + + let hasCanonicalizer = 1; +} + +def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Tdg gate to a qubit"; + let description = [{ + Applies a Tdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.tdg %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "tdg"; } + }]; + + let hasCanonicalizer = 1; +} + +def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SX gate to a qubit"; + let description = [{ + Applies an SX gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.sx %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sx"; } + }]; + + let hasCanonicalizer = 1; +} + +def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SXdg gate to a qubit"; + let description = [{ + Applies an SXdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.sxdg %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "sxdg"; } + }]; + + let hasCanonicalizer = 1; +} + +def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RX gate to a qubit"; + let description = [{ + Applies an RX gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.rx(%theta) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RY gate to a qubit"; + let description = [{ + Applies an RY gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.ry(%theta) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ry"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RZ gate to a qubit"; + let description = [{ + Applies an RZ gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.rz(%theta) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply a P gate to a qubit"; + let description = [{ + Applies a P gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.p(%theta) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "p"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply an R gate to a qubit"; + let description = [{ + Applies an R gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.r(%theta, %phi) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "r"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi)> + ]; + + let hasCanonicalizer = 1; +} + +def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply a U2 gate to a qubit"; + let description = [{ + Applies a U2 gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.u2(%phi, %lambda) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$phi, + Arg:$lambda); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "u2"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; + + let hasCanonicalizer = 1; +} + +def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { + let summary = "Apply a U gate to a qubit"; + let description = [{ + Applies a U gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = qco.u(%theta, %phi, %lambda) %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi, + Arg:$lambda); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "u"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; + + let hasCanonicalizer = 1; +} + +def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a SWAP gate to two qubits"; + let description = [{ + Applies a SWAP gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.swap %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "swap"; } + }]; + + let hasCanonicalizer = 1; +} + +def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a iSWAP gate to two qubits"; + let description = [{ + Applies a iSWAP gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.iswap %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "iswap"; } + }]; +} + +def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a DCX gate to two qubits"; + let description = [{ + Applies a DCX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.dcx %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "dcx"; } + }]; +} + +def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply an ECR gate to two qubits"; + let description = [{ + Applies an ECR gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.ecr %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ecr"; } + }]; + + let hasCanonicalizer = 1; +} + +def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RXX gate to two qubits"; + let description = [{ + Applies an RXX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.rxx(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rxx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RYY gate to two qubits"; + let description = [{ + Applies an RYY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.ryy(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "ryy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZX gate to two qubits"; + let description = [{ + Applies an RZX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.rzx(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rzx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZZ gate to two qubits"; + let description = [{ + Applies an RZZ gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.rzz(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "rzz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX+YY gate to two qubits"; + let description = [{ + Applies an XX+YY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.xx_plus_yy(%theta, %beta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "xx_plus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; + + let hasCanonicalizer = 1; +} + +def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX-YY gate to two qubits"; + let description = [{ + Applies an XX-YY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = qco.xx_minus_yy(%theta, %beta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + static StringRef getBaseSymbol() { return "xx_minus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; + + let hasCanonicalizer = 1; +} + +def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { + let summary = "Apply a barrier gate to a set of qubits"; + let description = [{ + Applies a barrier gate to a set of qubits and returns the transformed qubits. + + Example: + ```mlir + %q_out = qco.barrier %q_in : !qco.qubit -> !qco.qubit + ``` + }]; + + let arguments = (ins Arg, "the target qubits", [MemRead]>:$qubits_in); + let results = (outs Variadic:$qubits_out); + let assemblyFormat = "$qubits_in attr-dict `:` type($qubits_in) `->` type($qubits_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits(); + size_t getNumTargets(); + static size_t getNumControls(); + Value getInputQubit(size_t i); + Value getOutputQubit(size_t i); + Value getInputTarget(size_t i); + Value getOutputTarget(size_t i); + static Value getInputControl(size_t i); + static Value getOutputControl(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + static size_t getNumParams(); + static Value getParameter(size_t i); + static StringRef getBaseSymbol() { return "barrier"; } + }]; + + let builders = [ + OpBuilder<(ins "ValueRange":$qubits)> + ]; + + let hasCanonicalizer = 1; +} + +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +def YieldOp : QCOOp<"yield", traits = [Terminator]> { + let summary = "Yield from a modifier region"; + let description = [{ + Terminates a modifier region, yielding the transformed target qubits back to the enclosing modifier operation. + The targets must match the expected output signature of the modifier region. + + Example: + ```mlir + %ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in { + %tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit + qco.yield %tgt_q_res : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + ``` + }]; + + let arguments = (ins Variadic:$targets); + let assemblyFormat = "$targets attr-dict"; +} + +def CtrlOp : QCOOp<"ctrl", traits = + [ + UnitaryOpInterface, + AttrSizedOperandSegments, + AttrSizedResultSegments, + SameOperandsAndResultType, + SameOperandsAndResultShape, + SingleBlock, + RecursiveMemoryEffects + ]> { + let summary = "Add control qubits to a unitary operation"; + let description = [{ + A modifier operation that adds control qubits to the unitary operation + defined in its body region. The controlled operation applies the + underlying unitary only when all control qubits are in the |1⟩ state. + The operation takes a variadic number of control and target qubits as + inputs and produces corresponding output qubits. Control qubits are not + modified by the operation and simply pass through to the outputs. + + Example: + ```mlir + %ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in { + %tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit + qco.yield %tgt_q_res : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + ``` + }]; + + let arguments = (ins Arg, "the control qubits", [MemRead]>:$controls_in, + Arg, "the target qubits", [MemRead]>:$targets_in); + let results = (outs Variadic:$controls_out, Variadic:$targets_out); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + `(` $controls_in `)` $targets_in + $body attr-dict `:` + `(` `{` type($controls_in) `}` ( `,` `{` type($targets_in)^ `}` )? `)` + `->` + `(` `{` type($controls_out) `}` ( `,` `{` type($targets_out)^ `}` )? `)` + }]; + + let extraClassDeclaration = [{ + UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + Value getInputQubit(size_t i); + Value getOutputQubit(size_t i); + Value getInputTarget(size_t i); + Value getOutputTarget(size_t i); + Value getInputControl(size_t i); + Value getOutputControl(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + size_t getNumParams(); + Value getParameter(size_t i); + static StringRef getBaseSymbol() { return "ctrl"; } + }]; + + let builders = [ + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets), [{ + build($_builder, $_state, controls.getTypes(), targets.getTypes(), controls, targets); + }]>, + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "UnitaryOpInterface":$bodyUnitary)>, + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "const std::function&":$bodyBuilder)> + ]; + + let hasCanonicalizer = 1; + let hasVerifier = 1; +} + +#endif // QCOOPS diff --git a/mlir/include/mlir/Dialect/QCO/QCOUtils.h b/mlir/include/mlir/Dialect/QCO/QCOUtils.h new file mode 100644 index 0000000000..f03b2818b0 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/QCOUtils.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +namespace mlir::qco { + +/** + * @brief Remove a pair of inverse one-target, zero-parameter operations + * + * @tparam InverseOpType The type of the inverse operation. + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeInversePairOneTargetZeroParameter(OpType op, + mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the inverse operation + auto prevOp = op.getInputQubit(0).template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Trivialize both operations + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); + rewriter.replaceOp(op, op.getInputQubit(0)); + + return success(); +} + +/** + * @brief Remove a pair of inverse two-target, zero-parameter operations + * + * @tparam InverseOpType The type of the inverse operation. + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeInversePairTwoTargetZeroParameter(OpType op, + mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the inverse operation + auto prevOp = op.getInputQubit(0).template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { + return failure(); + } + + // Trivialize both operations + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); + rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); + + return success(); +} + +/** + * @brief Merge two compatible one-target, zero-parameter operations + * + * @details + * The two operations are replaced by a single operation corresponding to their + * square. + * + * @tparam SquareOpType The type of the square operation to be created. + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeOneTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getInputQubit(0).template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Replace operation with square operation + auto squareOp = + rewriter.create(op.getLoc(), op.getInputQubit(0)); + rewriter.replaceOp(op, squareOp.getOutputQubit(0)); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); + + return success(); +} + +/** + * @brief Merge two compatible one-target, one-parameter operations + * + * @details + * The new parameter is computed as the sum of the two original parameters. + * + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getInputQubit(0).template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new parameter + // For OneTargetOneParameter operations, the parameter has index 1 + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(1), prevOp.getOperand(1)); + op->setOperand(1, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); + + return success(); +} + +/** + * @brief Merge two compatible two-target, one-parameter operations + * + * @details + * The new parameter is computed as the sum of the two original parameters. + * + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getInputQubit(0).template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { + return failure(); + } + + // Compute and set new parameter + // For TwoTargetOneParameter operations, the parameter has index 2 + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); + + return success(); +} + +/** + * @brief Remove a trivial one-target, one-parameter operation + * + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + const auto paramAttr = OpType::getStaticParameter(op.getOperand(1)); + if (!paramAttr) { + return failure(); + } + + const auto paramValue = paramAttr.getValueAsDouble(); + if (std::abs(paramValue) > utils::TOLERANCE) { + return failure(); + } + + // Trivialize operation + rewriter.replaceOp(op, op.getInputQubit(0)); + + return success(); +} + +/** + * @brief Remove a trivial two-target, one-parameter operation + * + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + const auto paramAttr = OpType::getStaticParameter(op.getOperand(2)); + if (!paramAttr) { + return failure(); + } + + const auto paramValue = paramAttr.getValueAsDouble(); + if (std::abs(paramValue) > utils::TOLERANCE) { + return failure(); + } + + // Trivialize operation + rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); + + return success(); +} + +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h new file mode 100644 index 0000000000..753d359a6e --- /dev/null +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -0,0 +1,875 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +/** + * @brief Builder API for constructing QIR (Quantum Intermediate + * Representation) programs + * + * @details + * The QIRProgramBuilder provides a type-safe interface for constructing + * quantum programs in QIR format. Like QC, QIR uses reference semantics + * where operations modify qubits in place, but QIR programs require specific + * boilerplate structure including proper block organization and metadata + * attributes. + * + * @par QIR Base Profile Structure: + * QIR Base Profile compliant programs follow a specific 4-block structure: + * - Entry block: Constants and initialization (__quantum__rt__initialize) + * - Body block: Reversible quantum operations (gates) + * - Measurements block: Measurements, resets, deallocations + * - Output block: Output recording calls (array-based, grouped by register) + * + * @par Example Usage: + * ```c++ + * QIRProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Operations use QIR function calls + * builder.h(q0).cx(q0, q1); + * + * // Measure with register info for proper output recording + * auto c = builder.allocClassicalBitRegister(2, "c"); + * builder.measure(q0, c[0]); + * builder.measure(q1, c[1]); + * + * auto module = builder.finalize(); + * ``` + */ +class QIRProgramBuilder { +public: + /** + * @brief Construct a new QIRProgramBuilder + * @param context The MLIR context to use for building operations + */ + explicit QIRProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * Creates the main function with proper QIR structure (4-block layout), + * adds __quantum__rt__initialize call, and sets up the builder's insertion + * points. Must be called before adding operations. + */ + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + /** + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return An LLVM pointer representing the qubit + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * ``` + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * ``` + */ + Value staticQubit(int64_t index); + + /** + * @brief Allocate an array of (static) qubits + * @param size Number of qubits (must be positive) + * @return Vector of LLVM pointers representing the qubits + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3); + * ``` + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * %c1 = llvm.mlir.constant(1 : i64) : i64 + * %q1 = llvm.inttoptr %c1 : i64 to !llvm.ptr + * %c2 = llvm.mlir.constant(2 : i64) : i64 + * %q2 = llvm.inttoptr %c2 : i64 to !llvm.ptr + * ``` + */ + llvm::SmallVector allocQubitRegister(int64_t size); + + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + std::string registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + std::string name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + if (index < 0 || index >= size) { + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); + } + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + + /** + * @brief Allocate a classical bit register + * @param size Number of bits + * @param name Register name (default: "c") + * @return A ClassicalRegister structure + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(3, "c"); + * ``` + */ + ClassicalRegister allocClassicalBitRegister(int64_t size, + const std::string& name = "c"); + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit and record the result (simple version) + * + * @details + * Performs a Z-basis measurement using __quantum__qis__mz__body. The + * result is tracked for deferred output recording in the output block. + * This version does NOT include register information, so output will + * not be grouped by register. + * + * @param qubit The qubit to measure + * @param resultIndex The classical bit index for result pointer + * @return An LLVM pointer to the measurement result + * + * @par Example: + * ```c++ + * auto result = builder.measure(q0, 0); + * ``` + * ```mlir + * // In measurements block: + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %r = llvm.inttoptr %c0 : i64 to !llvm.ptr + * llvm.call @__quantum__qis__mz__body(%q0, %r) : (!llvm.ptr, !llvm.ptr) -> () + * + * // Output recording deferred to output block + * ``` + */ + Value measure(Value qubit, int64_t resultIndex); + + /** + * @brief Measure a qubit into a classical register + * + * @details + * Performs a Z-basis measurement using __quantum__qis__mz__body and tracks + * the measurement with register information for array-based output recording. + * Output recording is deferred to the output block during finalize(), where + * measurements are grouped by register and recorded using: + * 1. __quantum__rt__array_record_output for each register + * 2. __quantum__rt__result_record_output for each measurement in the register + * + * @param qubit The qubit to measure + * @param bit The classical bit to store the result + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(2, "c"); + * builder.measure(q0, c[0]); + * builder.measure(q1, c[1]); + * ``` + * ```mlir + * // In measurements block: + * llvm.call @__quantum__qis__mz__body(%q0, %r0) : (!llvm.ptr, !llvm.ptr) -> + * () llvm.call @__quantum__qis__mz__body(%q1, %r1) : (!llvm.ptr, !llvm.ptr) + * -> () + * + * // In output block (generated during finalize): + * @0 = internal constant [3 x i8] c"c\00" + * @1 = internal constant [5 x i8] c"c0r\00" + * @2 = internal constant [5 x i8] c"c1r\00" + * llvm.call @__quantum__rt__array_record_output(i64 2, ptr @0) + * llvm.call @__quantum__rt__result_record_output(ptr %r0, ptr @1) + * llvm.call @__quantum__rt__result_record_output(ptr %r1, ptr @2) + * ``` + */ + QIRProgramBuilder& measure(Value qubit, const Bit& bit); + + /** + * @brief Reset a qubit to |0⟩ state + * + * @details + * Resets a qubit using __quantum__qis__reset__body. + * + * @param qubit The qubit to reset + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.reset(q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& reset(Value qubit); + + //===--------------------------------------------------------------------===// + // Unitary Operations + //===--------------------------------------------------------------------===// + + // GPhaseOp + + /** + * @brief Apply a QIR gphase operation + * + * @param theta Rotation angle in radians + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.gphase(theta); + * ``` + * ```mlir + * llvm.call @__quantum__qis__gphase__body(%theta) : (f64) -> () + * ``` + */ + QIRProgramBuilder& gphase(const std::variant& theta); + + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q) : (!llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(id, i) + DECLARE_ONE_TARGET_ZERO_PARAMETER(x, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(y, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(z, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(h, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(s, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sdg, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(t, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(tdg, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sx, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sxdg, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM) : (!llvm.ptr, \ + * f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(rx, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(ry, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(rz, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(p, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // OneTargetTwoParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(r, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(u2, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, PARAM3, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM1, %PARAM2, \ + * %PARAM3) : \ + * (!llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM1, \ + * %PARAM2, %PARAM3) : (!llvm.ptr, !llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2, %PARAM3) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, f64) -> \ + * () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(u, u3, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param control Control qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(Value control, Value target0, Value target1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param controls Control qubits \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value target0, \ + Value target1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(swap, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iswap, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(dcx, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ecr, ecr) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER + + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target0, Value target1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %q3, \ + * %PARAM) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target0, \ + Value target1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(rxx, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(ryy, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(rzx, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(rzz, rzz, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1, %PARAM1, %PARAM2) \ + * : (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target0, Value target1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %q3, \ + * %PARAM1, %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64, \ + * f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target0, \ + Value target1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(xx_plus_yy, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(xx_minus_yy, xx_minus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Automatically deallocates all remaining allocated qubits, generates + * array-based output recording in the output block (grouped by register), + * ensures proper QIR metadata attributes are set, and transfers ownership + * of the module to the caller. The builder should not be used after calling + * this method. + * + * @return OwningOpRef containing the constructed QIR program module + */ + OwningOpRef finalize(); + +private: + OpBuilder builder; + ModuleOp module; + Location loc; + + LLVM::LLVMFuncOp mainFunc; + + /// Allocator and StringSaver for stable StringRefs + llvm::BumpPtrAllocator allocator; + llvm::StringSaver stringSaver{allocator}; + + /// Entry block: constants and initialization + Block* entryBlock{}; + /// Body block: reversible operations (gates) + Block* bodyBlock{}; + /// Measurements block: measurements, resets, deallocations + Block* measurementsBlock{}; + /// Output block: output recording calls + Block* outputBlock{}; + + /// Exit code constant (created in entry block, used in output block) + LLVM::ConstantOp exitCode; + + /// Cache static pointers for reuse + llvm::DenseMap ptrCache; + + /// Map from (register_name, register_index) to result pointer + llvm::DenseMap, Value> registerResultMap; + + /// Track qubit and result counts for QIR metadata + QIRMetadata metadata_; + + /** + * @brief Helper to create a LLVM CallOp + * + * @param parameters Operation parameters + * @param controls Control qubits + * @param targets Target qubits + * @param fnName Name of the QIR function to call + */ + void + createCallOp(const llvm::SmallVector>& parameters, + ValueRange controls, const SmallVector& targets, + llvm::StringRef fnName); + + /** + * @brief Generate array-based output recording in the output block + * + * @details + * Called by finalize() to generate output recording calls for all tracked + * measurements. Groups measurements by register and generates: + * 1. array_record_output for each register + * 2. result_record_output for each measurement in the register + */ + void generateOutputRecording(); + + bool isFinalized = false; + + /// Check if the builder has been finalized + void checkFinalized() const; +}; + +} // namespace mlir::qir diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h new file mode 100644 index 0000000000..7ef8071360 --- /dev/null +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace mlir::qir { + +// QIR function names + +inline constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; +inline constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; +inline constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; +inline constexpr auto QIR_ARRAY_RECORD_OUTPUT = + "__quantum__rt__array_record_output"; +inline constexpr auto QIR_RESET = "__quantum__qis__reset__body"; + +inline constexpr auto QIR_GPHASE = "__quantum__qis__gphase__body"; + +#define ADD_STANDARD_GATE(NAME_BIG, NAME_SMALL) \ + inline constexpr auto QIR_##NAME_BIG = \ + "__quantum__qis__" #NAME_SMALL "__body"; \ + inline constexpr auto QIR_C##NAME_BIG = \ + "__quantum__qis__c" #NAME_SMALL "__body"; \ + inline constexpr auto QIR_CC##NAME_BIG = \ + "__quantum__qis__cc" #NAME_SMALL "__body"; \ + inline constexpr auto QIR_CCC##NAME_BIG = \ + "__quantum__qis__ccc" #NAME_SMALL "__body"; + +ADD_STANDARD_GATE(I, i) +ADD_STANDARD_GATE(X, x) +ADD_STANDARD_GATE(Y, y) +ADD_STANDARD_GATE(Z, z) +ADD_STANDARD_GATE(H, h) +ADD_STANDARD_GATE(S, s) +ADD_STANDARD_GATE(SDG, sdg) +ADD_STANDARD_GATE(T, t) +ADD_STANDARD_GATE(TDG, tdg) +ADD_STANDARD_GATE(SX, sx) +ADD_STANDARD_GATE(SXDG, sxdg) +ADD_STANDARD_GATE(RX, rx) +ADD_STANDARD_GATE(RY, ry) +ADD_STANDARD_GATE(RZ, rz) +ADD_STANDARD_GATE(P, p) +ADD_STANDARD_GATE(R, r) +ADD_STANDARD_GATE(U2, u2) +ADD_STANDARD_GATE(U, u3) +ADD_STANDARD_GATE(SWAP, swap) +ADD_STANDARD_GATE(ISWAP, iswap) +ADD_STANDARD_GATE(DCX, dcx) +ADD_STANDARD_GATE(ECR, ecr) +ADD_STANDARD_GATE(RXX, rxx) +ADD_STANDARD_GATE(RYY, ryy) +ADD_STANDARD_GATE(RZX, rzx) +ADD_STANDARD_GATE(RZZ, rzz) +ADD_STANDARD_GATE(XXPLUSYY, xx_plus_yy) +ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) + +#undef ADD_STANDARD_GATE + +// Functions for getting QIR function names + +#define DEFINE_GETTER(NAME) \ + /** \ + * @brief Gets the QIR function name for NAME \ + * \ + * @param numControls Number of control qubits \ + * @return The QIR function name \ + */ \ + inline StringRef getFnName##NAME(size_t numControls) { \ + switch (numControls) { \ + case 0: \ + return QIR_##NAME; \ + case 1: \ + return QIR_C##NAME; \ + case 2: \ + return QIR_CC##NAME; \ + case 3: \ + return QIR_CCC##NAME; \ + default: \ + llvm::reportFatalUsageError( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + } + +DEFINE_GETTER(I) +DEFINE_GETTER(X) +DEFINE_GETTER(Y) +DEFINE_GETTER(Z) +DEFINE_GETTER(H) +DEFINE_GETTER(S) +DEFINE_GETTER(SDG) +DEFINE_GETTER(T) +DEFINE_GETTER(TDG) +DEFINE_GETTER(SX) +DEFINE_GETTER(SXDG) +DEFINE_GETTER(RX) +DEFINE_GETTER(RY) +DEFINE_GETTER(RZ) +DEFINE_GETTER(P) +DEFINE_GETTER(R) +DEFINE_GETTER(U2) +DEFINE_GETTER(U) +DEFINE_GETTER(SWAP) +DEFINE_GETTER(ISWAP) +DEFINE_GETTER(DCX) +DEFINE_GETTER(ECR) +DEFINE_GETTER(RXX) +DEFINE_GETTER(RYY) +DEFINE_GETTER(RZX) +DEFINE_GETTER(RZZ) +DEFINE_GETTER(XXPLUSYY) +DEFINE_GETTER(XXMINUSYY) + +#undef DEFINE_GETTER + +/** + * @brief State object for tracking QIR metadata during conversion + * + * @details + * This struct maintains metadata about the QIR program being built: + * - Qubit and result counts for QIR metadata + * - Whether dynamic memory management is needed + */ +struct QIRMetadata { + /// Number of qubits used in the module + size_t numQubits{0}; + /// Number of measurement results stored in the module + size_t numResults{0}; + /// Whether the module uses dynamic qubit management + bool useDynamicQubit{false}; + /// Whether the module uses dynamic result management + bool useDynamicResult{false}; +}; + +/** + * @brief Find the main LLVM function with entry_point attribute + * + * @details + * Searches for the LLVM function marked with the "entry_point" attribute in + * the passthrough attributes. + * + * @param op The module operation to search in + * @return The main LLVM function, or nullptr if not found + */ +LLVM::LLVMFuncOp getMainFunction(Operation* op); + +/** + * @brief Set QIR base profile metadata attributes on the main function + * + * @details + * Adds the required metadata attributes for QIR base profile compliance: + * - `entry_point`: Marks the main entry point function + * - `output_labeling_schema`: labeled + * - `qir_profiles`: base_profile + * - `required_num_qubits`: Number of qubits used + * - `required_num_results`: Number of measurement results + * - `qir_major_version`: 2 + * - `qir_minor_version`: 0 + * - `dynamic_qubit_management`: true/false + * - `dynamic_result_management`: true/false + * + * These attributes are required by the QIR specification and inform QIR + * consumers about the module's resource requirements and capabilities. + * + * @param main The main LLVM function to annotate + * @param metadata The QIR metadata containing qubit/result counts + */ +void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata); + +/** + * @brief Get or create a QIR function declaration + * + * @details + * Searches for an existing function declaration in the symbol table. If not + * found, creates a new function declaration at the end of the module. + * + * For QIR functions that are irreversible (measurement and reset), the + * "irreversible" attribute is added automatically. + * + * @param builder The builder to use for creating operations + * @param op The operation requesting the function (for context) + * @param fnName The name of the QIR function + * @param fnType The LLVM function type signature + * @return The LLVM function declaration + */ +LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, + Operation* op, StringRef fnName, + Type fnType); + +/** + * @brief Create a global string constant for result labeling + * + * @details + * Creates a global string constant at the module level and inserts an + * AddressOfOp at the start of the main function's entry block. + * + * @param builder The builder to use for creating operations + * @param op The operation requesting the label (for context/location) + * @param label The label string (e.g., "r0") + * @param symbolPrefix The prefix for the symbol name (default: + * "qir.result_label") + * @return AddressOf operation for the global constant + */ +LLVM::AddressOfOp +createResultLabel(OpBuilder& builder, Operation* op, StringRef label, + StringRef symbolPrefix = "qir.result_label"); + +/** + * @brief Create a pointer value from an integer index + * + * @details + * Creates a constant operation with the given index and converts it to a + * pointer using inttoptr. This is used for static qubit/result references in + * QIR. + * + * @param builder The builder to use for creating operations + * @param loc The location for the operations + * @param index The integer index + * @return The pointer value + */ +Value createPointerFromIndex(OpBuilder& builder, Location loc, int64_t index); + +} // namespace mlir::qir diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h new file mode 100644 index 0000000000..adfb8fd3f3 --- /dev/null +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace mlir::utils { + +constexpr auto TOLERANCE = 1e-15; + +/** + * @brief Convert a variant parameter (double or Value) to a Value + * + * @param builder The operation builder. + * @param state The operation state. + * @param parameter The parameter as a variant (double or Value). + * @return Value The parameter as a Value. + */ +inline Value variantToValue(OpBuilder& builder, const OperationState& state, + const std::variant& parameter) { + Value operand; + if (std::holds_alternative(parameter)) { + operand = builder.create( + state.location, builder.getF64FloatAttr(std::get(parameter))); + } else { + operand = std::get(parameter); + } + return operand; +} + +} // namespace mlir::utils diff --git a/mlir/include/mlir/Support/PrettyPrinting.h b/mlir/include/mlir/Support/PrettyPrinting.h new file mode 100644 index 0000000000..f1c8b2934c --- /dev/null +++ b/mlir/include/mlir/Support/PrettyPrinting.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +namespace mlir { + +/** + * @brief Calculate UTF-8 display width of a string + * + * @details + * Counts the visual display width, not byte count. UTF-8 multi-byte + * characters like → and ✓ are counted as 1 display column. + * + * @param str The string to measure + * @return The display width in columns + */ +int calculateDisplayWidth(const std::string& str); + +/** + * @brief Wrap a long line into multiple lines that fit within the box + * + * @details + * Splits a line that's too long into multiple lines, preferring to break + * at whitespace when possible. Each wrapped line will fit within the + * available width inside the box. + * + * @param line The line to wrap + * @param maxWidth Maximum width for each line (excluding box borders and + * indent) + * @param indent Number of spaces to indent wrapped lines + * @return Vector of wrapped lines + */ +std::vector wrapLine(const std::string& line, int maxWidth, + int indent = 0); + +/** + * @brief Print top border of a box + * + * @param os Output stream to write to + */ +void printBoxTop(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print middle separator of a box + * + * @param os Output stream to write to + */ +void printBoxMiddle(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print bottom border of a box + * + * @param os Output stream to write to + */ +void printBoxBottom(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print a box line with text and proper padding + * + * @details + * If the text is too long, it will be wrapped across multiple lines. + * + * @param text The text to display in the box + * @param indent Number of spaces to indent the text (0 for left-aligned) + * @param os Output stream to write to + */ +void printBoxLine(const std::string& text, int indent = 0, + llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print multiple lines of text within the box, with line wrapping + * + * @details + * Takes a multi-line string and prints each line within the box borders, + * wrapping long lines as needed. + * + * @param text The text to display (may contain newlines) + * @param indent Number of spaces to indent the text + * @param os Output stream to write to + */ +void printBoxText(const std::string& text, int indent = 0, + llvm::raw_ostream& os = llvm::errs()); + +} // namespace mlir diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 9add328fef..8b2ef74e7c 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -8,3 +8,5 @@ add_subdirectory(Dialect) add_subdirectory(Conversion) +add_subdirectory(Compiler) +add_subdirectory(Support) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt new file mode 100644 index 0000000000..1e4483754d --- /dev/null +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Build the compiler pipeline library +add_mlir_library( + MQTCompilerPipeline + CompilerPipeline.cpp + ADDITIONAL_HEADER_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Compiler + LINK_LIBS + PUBLIC + MLIRPass + MLIRTransforms + MLIRTransformUtils + QCToQCO + QCOToQC + QCToQIR + MQT::MLIRSupport + MQT::ProjectOptions) + +# collect header files +file(GLOB_RECURSE COMPILER_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Compiler/*.h") + +target_sources(MQTCompilerPipeline PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${COMPILER_HEADERS_SOURCE}) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp new file mode 100644 index 0000000000..d8cbd3b8be --- /dev/null +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Compiler/CompilerPipeline.h" + +#include "mlir/Conversion/QCOToQC/QCOToQC.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Conversion/QCToQIR/QCToQIR.h" +#include "mlir/Support/PrettyPrinting.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { + +/** + * @brief Pretty print IR with ASCII art borders and stage identifier + * + * @param module The module to print + * @param stageName Name of the compilation stage + * @param stageNumber Current stage number + * @param totalStages Total number of stages (for progress indication) + */ +static void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, + const int stageNumber, const int totalStages) { + llvm::errs() << "\n"; + printBoxTop(); + + // Build the stage header + const std::string stageHeader = "Stage " + std::to_string(stageNumber) + "/" + + std::to_string(totalStages) + ": " + + stageName.str(); + printBoxLine(stageHeader); + + printBoxMiddle(); + + // Capture the IR to a string so we can wrap it in box lines + std::string irString; + llvm::raw_string_ostream irStream(irString); + module.print(irStream); + + // Print the IR with box lines and wrapping + printBoxText(irString); + + printBoxBottom(); + llvm::errs().flush(); +} + +void QuantumCompilerPipeline::addCleanupPasses(PassManager& pm) { + // Always run canonicalization and dead value removal + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); +} + +void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { + // Enable timing statistics if requested + if (config_.enableTiming) { + pm.enableTiming(); + } + + // Enable pass statistics if requested + if (config_.enableStatistics) { + pm.enableStatistics(); + } +} + +LogicalResult +QuantumCompilerPipeline::runPipeline(ModuleOp module, + CompilationRecord* record) const { + // Ensure printIRAfterAllStages implies recordIntermediates + if (config_.printIRAfterAllStages && + (!config_.recordIntermediates || record == nullptr)) { + llvm::errs() << "printIRAfterAllStages requires recordIntermediates to be " + "enabled and the record pointer to be non-null.\n"; + return failure(); + } + + PassManager pm(module.getContext()); + + // Configure PassManager with diagnostic options + configurePassManager(pm); + + // Determine total number of stages for progress indication + // 1. QC import + // 2. QC canonicalization + // 3. QC-to-QCO conversion + // 4. QCO canonicalization + // 5. Optimization passes + // 6. QCO canonicalization + // 7. QCO-to-QC conversion + // 8. QC canonicalization + // 9. QC-to-QIR conversion (optional) + // 10. QIR canonicalization (optional) + auto totalStages = 8; + if (config_.convertToQIR) { + totalStages += 2; + } + auto currentStage = 0; + + // Stage 1: QC import + if (record != nullptr && config_.recordIntermediates) { + record->afterQCImport = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "QC Import", ++currentStage, totalStages); + } + } + + // Stage 2: QC canonicalization + addCleanupPasses(pm); + if (pm.run(module).failed()) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterInitialCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Initial QC Canonicalization", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 3: QC-to-QCO conversion + pm.addPass(createQCToQCO()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQCOConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "QC → QCO Conversion", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 4: QCO canonicalization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQCOCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Initial QCO Canonicalization", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 5: Optimization passes + // TODO: Add optimization passes + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimization = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Optimization Passes", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 6: QCO canonicalization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimizationCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Final QCO Canonicalization", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 7: QCO-to-QC conversion + pm.addPass(createQCOToQC()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQCConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "QCO → QC Conversion", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 8: QC canonicalization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQCCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Final QC Canonicalization", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 9: QC-to-QIR conversion (optional) + if (config_.convertToQIR) { + pm.addPass(createQCToQIR()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQIRConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "QC → QIR Conversion", ++currentStage, + totalStages); + } + } + pm.clear(); + + // Stage 10: QIR canonicalization (optional) + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQIRCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "QIR Canonicalization", ++currentStage, + totalStages); + } + } + } + + // Print compilation summary + if (config_.printIRAfterAllStages) { + llvm::errs() << "\n"; + printBoxTop(); + + printBoxLine("✓ Compilation Complete"); + + const std::string summaryLine = + "Successfully processed " + std::to_string(currentStage) + " stages"; + printBoxLine(summaryLine, 1); // Indent by 1 space + + printBoxBottom(); + llvm::errs() << "\n"; + llvm::errs().flush(); + } + return success(); +} + +std::string captureIR(ModuleOp module) { + std::string result; + llvm::raw_string_ostream os(result); + module.print(os); + return result; +} + +} // namespace mlir diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt index 98d9c3b8f0..7f095140a1 100644 --- a/mlir/lib/Conversion/CMakeLists.txt +++ b/mlir/lib/Conversion/CMakeLists.txt @@ -10,3 +10,6 @@ add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) +add_subdirectory(QCOToQC) +add_subdirectory(QCToQCO) +add_subdirectory(QCToQIR) diff --git a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp index 28a8ed910f..2c0b993e91 100644 --- a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp +++ b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp @@ -44,34 +44,32 @@ using namespace mlir; #define GEN_PASS_DEF_MQTOPTTOMQTREF #include "mlir/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.h.inc" -namespace { - -bool isQubitType(const MemRefType type) { +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::StoreOp op) { +static bool isQubitType(memref::StoreOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - class MQTOptToMQTRefTypeConverter final : public TypeConverter { public: explicit MQTOptToMQTRefTypeConverter(MLIRContext* ctx) { diff --git a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp index e7903e7096..fcfb8f18fd 100644 --- a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp +++ b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp @@ -74,26 +74,28 @@ class StatefulOpConversionPattern : public mlir::OpConversionPattern { LoweringState* state_; }; -bool isQubitType(const MemRefType type) { +} // namespace + +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - class MQTRefToMQTOptTypeConverter final : public TypeConverter { public: explicit MQTRefToMQTOptTypeConverter(MLIRContext* ctx) { diff --git a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp index 81b9811e88..59977c8a00 100644 --- a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp +++ b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp @@ -62,8 +62,6 @@ using namespace mlir; #define GEN_PASS_DEF_MQTREFTOQIR #include "mlir/Conversion/MQTRefToQIR/MQTRefToQIR.h.inc" -namespace { - /** * @brief Look up the function declaration with a given name. If it does not exist create one and return it. @@ -74,9 +72,9 @@ namespace { * @param fnType The type signature of the function. * @return The LLVM funcOp declaration with the requested name and signature. */ -LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, - Operation* op, StringRef fnName, - Type fnType) { +static LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, + Operation* op, StringRef fnName, + Type fnType) { // check if the function already exists auto* fnDecl = SymbolTable::lookupNearestSymbolFrom(op, rewriter.getStringAttr(fnName)); @@ -102,6 +100,8 @@ LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, return static_cast(fnDecl); } +namespace { + struct LoweringState { // map a given index to a pointer value, to reuse the value instead of // creating a new one every time @@ -136,26 +136,28 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; -bool isQubitType(const MemRefType type) { +} // namespace + +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - struct MQTRefToQIRTypeConverter final : LLVMTypeConverter { explicit MQTRefToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { // QubitType conversion diff --git a/mlir/lib/Conversion/QCOToQC/CMakeLists.txt b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt new file mode 100644 index 0000000000..1c4a8d1f0a --- /dev/null +++ b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + QCOToQC + ${CONVERSION_SOURCES} + DEPENDS + QCOToQCIncGen + LINK_LIBS + PUBLIC + MLIRQCDialect + MLIRQCODialect + MLIRArithDialect + MLIRFuncDialect) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp new file mode 100644 index 0000000000..9e89791b02 --- /dev/null +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QCOToQC/QCOToQC.h" + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { +using namespace qco; +using namespace qc; + +#define GEN_PASS_DEF_QCOTOQC +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" + +/** + * @brief Converts a zero-target, one-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @param op The QCO operation instance to convert + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertZeroTargetOneParameter(QCOOpType& op, + ConversionPatternRewriter& rewriter) { + rewriter.create(op.getLoc(), op.getParameter(0)); + rewriter.eraseOp(op); + return success(); +} + +/** + * @brief Converts a one-target, zero-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit); + + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); + + return success(); +} + +/** + * @brief Converts a one-target, one-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0)); + + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); + + return success(); +} + +/** + * @brief Converts a one-target, two-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0), + op.getParameter(1)); + + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); + + return success(); +} + +/** + * @brief Converts a one-target, three-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetThreeParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); + + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); + + return success(); +} + +/** + * @brief Converts a two-target, zero-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1); + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); + + return success(); +} + +/** + * @brief Converts a two-target, one-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1, + op.getParameter(0)); + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); + + return success(); +} + +/** + * @brief Converts a two-target, two-parameter QCO operation to QC + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); + + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1, op.getParameter(0), + op.getParameter(1)); + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); + + return success(); +} + +/** + * @brief Type converter for QCO-to-QC conversion + * + * @details + * Handles type conversion between the QCO and QC dialects. + * The primary conversion is from !qco.qubit to !qc.qubit, which + * represents the semantic shift from value types to reference types. + * + * Other types (integers, booleans, etc.) pass through unchanged via + * the identity conversion. + */ +class QCOToQCTypeConverter final : public TypeConverter { +public: + explicit QCOToQCTypeConverter(MLIRContext* ctx) { + // Identity conversion for all types by default + addConversion([](Type type) { return type; }); + + // Convert QCO qubit values to QC qubit references + addConversion([ctx](qco::QubitType /*type*/) -> Type { + return qc::QubitType::get(ctx); + }); + } +}; + +/** + * @brief Converts qco.alloc to qc.alloc + * + * @details + * Allocates a new qubit initialized to the |0⟩ state. Register metadata + * (name, size, index) is preserved during conversion. + * + * The conversion is straightforward: the QCO allocation produces an SSA + * value, while the QC allocation produces a reference. MLIR's type + * conversion system automatically handles the semantic shift. + * + * Example transformation: + * ```mlir + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit + * // becomes: + * %q = qc.alloc("q", 3, 0) : !qc.qubit + * ``` + */ +struct ConvertQCOAllocOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + // Create qc.alloc with preserved register metadata + rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), + op.getRegisterIndexAttr()); + + return success(); + } +}; + +/** + * @brief Converts qco.dealloc to qc.dealloc + * + * @details + * Deallocates a qubit, releasing its resources. The OpAdaptor automatically + * provides the type-converted qubit operand (!qc.qubit instead of + * !qco.qubit), so we simply pass it through to the new operation. + * + * Example transformation: + * ```mlir + * qco.dealloc %q_qco : !qco.qubit + * // becomes: + * qc.dealloc %q_qc : !qc.qubit + * ``` + */ +struct ConvertQCODeallocOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted qubit + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + return success(); + } +}; + +/** + * @brief Converts qco.static to qc.static + * + * @details + * Static qubits represent references to hardware-mapped or fixed-position + * qubits identified by an index. The conversion preserves the index attribute + * and creates the corresponding qc.static operation. + * + * Example transformation: + * ```mlir + * %q0 = qco.static 0 : !qco.qubit + * // becomes: + * %q = qc.static 0 : !qc.qubit + * ``` + */ +struct ConvertQCOStaticOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + // Create qc.static with the same index + rewriter.replaceOpWithNewOp(op, op.getIndex()); + return success(); + } +}; + +/** + * @brief Converts qco.measure to qc.measure + * + * @details + * Measurement demonstrates the key semantic difference between the dialects: + * - QCO (value semantics): Consumes input qubit, returns both output qubit + * and classical bit result + * - QC (reference semantics): Measures qubit in-place, returns only the + * classical bit result + * + * The OpAdaptor provides the input qubit already converted to !qc.qubit. + * Since QC operations are in-place, we return the same qubit reference + * alongside the measurement bit. MLIR's conversion infrastructure automatically + * routes subsequent uses of the QCO output qubit to this QC reference. + * + * Register metadata (name, size, index) for output recording is preserved + * during conversion. + * + * Example transformation: + * ```mlir + * %q_out, %c = qco.measure("c", 2, 0) %q_in : !qco.qubit + * // becomes: + * %c = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 + * // %q_out uses are replaced with %q (the adaptor-converted input) + * ``` + */ +struct ConvertQCOMeasureOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create qc.measure (in-place operation, returns only bit) + // Preserve register metadata for output recording + auto qcOp = rewriter.create( + op.getLoc(), qcQubit, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); + + auto measureBit = qcOp.getResult(); + + // Replace both results: qubit output → same qc reference, bit → new bit + rewriter.replaceOp(op, {qcQubit, measureBit}); + + return success(); + } +}; + +/** + * @brief Converts qco.reset to qc.reset + * + * @details + * Reset operations force a qubit to the |0⟩ state: + * - QCO (value semantics): Consumes input qubit, returns reset output qubit + * - QC (reference semantics): Resets qubit in-place, no result value + * + * The OpAdaptor provides the input qubit already converted to !qc.qubit. + * Since QC's reset is in-place, we return the same qubit reference. + * MLIR's conversion infrastructure automatically routes subsequent uses of + * the QCO output qubit to this QC reference. + * + * Example transformation: + * ```mlir + * %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit + * // becomes: + * qc.reset %q : !qc.qubit + * // %q_out uses are replaced with %q (the adaptor-converted input) + * ``` + */ +struct ConvertQCOResetOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::ResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& qcQubit = adaptor.getQubitIn(); + + // Create qc.reset (in-place operation, no result) + rewriter.create(op.getLoc(), qcQubit); + + // Replace the output qubit with the same qc reference + rewriter.replaceOp(op, qcQubit); + + return success(); + } +}; + +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qco.OP_NAME(%PARAM) \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM) \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertZeroTargetOneParameter(op, rewriter); \ + } \ + }; + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME %q : !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetZeroParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetOneParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetTwoParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit \ + * -> !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetThreeParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetZeroParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetOneParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + */ \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetTwoParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +/** + * @brief Converts qco.barrier to qc.barrier + * + * @par Example: + * ```mlir + * %q0_out, %q1_out = qco.barrier %q0_in, %q1_in : !qco.qubit, !qco.qubit -> + * !qco.qubit, !qco.qubit + * ``` + * is converted to + * ```mlir + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit + * ``` + */ +struct ConvertQCOBarrierOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::BarrierOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted qubits + const auto& qcQubits = adaptor.getQubitsIn(); + + // Create qc.barrier operation + rewriter.create(op.getLoc(), qcQubits); + + // Replace the output qubits with the same qc references + rewriter.replaceOp(op, qcQubits); + + return success(); + } +}; + +/** + * @brief Converts qco.ctrl to qc.ctrl + * + * @par Example: + * ```mlir + * %controls_out, %targets_out = qco.ctrl({%q0_in}, {%q1_in}) { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + * ``` + * is converted to + * ```mlir + * qc.ctrl(%q0) { + * qc.x %q1 : !qc.qubit + * } + * ``` + */ +struct ConvertQCOCtrlOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::CtrlOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Get QC controls + const auto& qcControls = adaptor.getControlsIn(); + + // Create qc.ctrl operation + auto qcoOp = rewriter.create(op.getLoc(), qcControls); + + // Clone body region from QCO to QC + auto& dstRegion = qcoOp.getBody(); + rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, adaptor.getOperands()); + + return success(); + } +}; + +/** + * @brief Converts qco.yield to qc.yield + * + * @par Example: + * ```mlir + * qco.yield %targets + * ``` + * is converted to + * ```mlir + * qc.yield + * ``` + */ +struct ConvertQCOYieldOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::YieldOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.replaceOpWithNewOp(op); + return success(); + } +}; + +/** + * @brief Pass implementation for QCO-to-QC conversion + * + * @details + * This pass converts QCO dialect operations (value semantics) to + * QC dialect operations (reference semantics). The conversion is useful + * for lowering optimized SSA-form code back to a hardware-oriented + * representation suitable for backend code generation. + * + * The conversion leverages MLIR's built-in type conversion infrastructure: + * The TypeConverter handles !qco.qubit → !qc.qubit transformations, + * and the OpAdaptor automatically provides type-converted operands to each + * conversion pattern. This eliminates the need for manual state tracking. + * + * Key semantic transformation: + * - QCO operations form explicit SSA chains where each operation consumes + * inputs and produces new outputs + * - QC operations modify qubits in-place using references + * - The conversion maps each QCO SSA chain to a single QC reference, + * with MLIR's conversion framework automatically handling the plumbing + * + * The pass operates through: + * 1. Type conversion: !qco.qubit → !qc.qubit + * 2. Operation conversion: Each QCO op converted to its QC equivalent + * 3. Automatic operand mapping: OpAdaptors provide converted operands + * 4. Function/control-flow adaptation: Signatures updated to use QC types + */ +struct QCOToQC final : impl::QCOToQCBase { + using QCOToQCBase::QCOToQCBase; + + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + ConversionTarget target(*context); + RewritePatternSet patterns(context); + QCOToQCTypeConverter typeConverter(context); + + // Configure conversion target: QCO illegal, QC legal + target.addIllegalDialect(); + target.addLegalDialect(); + + // Register operation conversion patterns + // Note: No state tracking needed - OpAdaptors handle type conversion + patterns.add( + typeConverter, context); + + // Conversion of qco types in func.func signatures + // Note: This currently has limitations with signature changes + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + + // Conversion of qco types in func.return + populateReturnOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + + // Conversion of qco types in func.call + populateCallOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::CallOp op) { return typeConverter.isLegal(op); }); + + // Conversion of qco types in control-flow ops (e.g., cf.br, cf.cond_br) + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + + // Apply the conversion + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mlir diff --git a/mlir/lib/Conversion/QCToQCO/CMakeLists.txt b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt new file mode 100644 index 0000000000..a4ffcfebbe --- /dev/null +++ b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + QCToQCO + ${CONVERSION_SOURCES} + DEPENDS + QCToQCOIncGen + LINK_LIBS + PUBLIC + MLIRQCDialect + MLIRQCODialect + MLIRArithDialect + MLIRFuncDialect) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp new file mode 100644 index 0000000000..224afb3c9e --- /dev/null +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -0,0 +1,1252 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QCToQCO/QCToQCO.h" + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { +using namespace qco; +using namespace qc; + +#define GEN_PASS_DEF_QCTOQCO +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" + +namespace { + +/** + * @brief State object for tracking qubit value flow during conversion + * + * @details + * This struct maintains the mapping between QC dialect qubits (which use + * reference semantics) and their corresponding QCO dialect qubit values + * (which use value semantics). As the conversion progresses, each QC + * qubit reference is mapped to its latest QCO SSA value. + * + * The key insight is that QC operations modify qubits in-place: + * ```mlir + * %q = qc.alloc : !qc.qubit + * qc.h %q : !qc.qubit // modifies %q in-place + * qc.x %q : !qc.qubit // modifies %q in-place + * ``` + * + * While QCO operations consume inputs and produce new outputs: + * ```mlir + * %q0 = qco.alloc : !qco.qubit + * %q1 = qco.h %q0 : !qco.qubit -> !qco.qubit // %q0 consumed, %q1 produced + * %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit // %q1 consumed, %q2 produced + * ``` + * + * The qubitMap tracks that the QC qubit %q corresponds to: + * - %q0 after allocation + * - %q1 after the H gate + * - %q2 after the X gate + */ +struct LoweringState { + /// Map from original QC qubit references to their latest QCO SSA values + llvm::DenseMap qubitMap; + + /// Modifier information + int64_t inCtrlOp = 0; + DenseMap> targetsIn; + DenseMap> targetsOut; +}; + +/** + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which tracks the mapping from reference-semantics QC qubits + * to value-semantics QCO qubits across multiple pattern applications. + * + * This stateful approach is necessary because the conversion needs to: + * 1. Track which QCO value corresponds to each QC qubit reference + * 2. Update these mappings as operations transform qubits + * 3. Share this information across different conversion patterns + * + * @tparam OpType The QC operation type to convert + */ +template +class StatefulOpConversionPattern : public OpConversionPattern { + +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, + MLIRContext* context, LoweringState* state) + : OpConversionPattern(typeConverter, context), state_(state) {} + + /// Returns the shared lowering state object + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; + +} // namespace + +/** + * @brief Converts a zero-target, one-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertZeroTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + const auto inCtrlOp = state.inCtrlOp; + + rewriter.create(op.getLoc(), op.getParameter(0)); + + // Update the state + if (inCtrlOp != 0) { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut; + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a one-target, zero-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); + qcoQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit] = qcoOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({qcoOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a one-target, one-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); + qcoQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit, op.getParameter(0)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit] = qcoOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({qcoOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a one-target, two-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertOneTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); + qcoQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create( + op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit] = qcoOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({qcoOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a one-target, three-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult convertOneTargetThreeParameter( + QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); + qcoQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit] = qcoOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({qcoOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a two-target, zero-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); + const auto& targetsIn = state.targetsIn[inCtrlOp]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a two-target, one-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); + const auto& targetsIn = state.targetsIn[inCtrlOp]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, + op.getParameter(0)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Converts a two-target, two-parameter QC operation to QCO + * + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertTwoTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; + if (inCtrlOp == 0) { + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; + } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); + const auto& targetsIn = state.targetsIn[inCtrlOp]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; + } + + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, + op.getParameter(0), op.getParameter(1)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + +/** + * @brief Type converter for QC-to-QCO conversion + * + * @details + * Handles type conversion between the QC and QCO dialects. + * The primary conversion is from !qc.qubit to !qco.qubit, which + * represents the semantic shift from reference types to value types. + * + * Other types (integers, booleans, etc.) pass through unchanged via + * the identity conversion. + */ +class QCToQCOTypeConverter final : public TypeConverter { +public: + explicit QCToQCOTypeConverter(MLIRContext* ctx) { + // Identity conversion for all types by default + addConversion([](Type type) { return type; }); + + // Convert QC qubit references to QCO qubit values + addConversion([ctx](qc::QubitType /*type*/) -> Type { + return qco::QubitType::get(ctx); + }); + } +}; + +/** + * @brief Converts qc.alloc to qco.alloc + * + * @details + * Allocates a new qubit and establishes the initial mapping in the state. + * Both dialects initialize qubits to the |0⟩ state. + * + * Register metadata (name, size, index) is preserved during conversion, + * allowing the QCO representation to maintain register information for + * debugging and visualization. + * + * Example transformation: + * ```mlir + * %q = qc.alloc("q", 3, 0) : !qc.qubit + * // becomes: + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit + * ``` + */ +struct ConvertQCAllocOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& qubitMap = getState().qubitMap; + const auto& qcQubit = op.getResult(); + + // Create the qco.alloc operation with preserved register metadata + auto qcoOp = rewriter.replaceOpWithNewOp( + op, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), + op.getRegisterIndexAttr()); + + const auto& qcoQubit = qcoOp.getResult(); + + // Establish initial mapping: this QC qubit reference now corresponds + // to this QCO SSA value + qubitMap.try_emplace(qcQubit, qcoQubit); + + return success(); + } +}; + +/** + * @brief Converts qc.dealloc to qco.dealloc + * + * @details + * Deallocates a qubit by looking up its latest QCO value and creating + * a corresponding qco.dealloc operation. The mapping is removed from + * the state as the qubit is no longer in use. + * + * Example transformation: + * ```mlir + * qc.dealloc %q : !qc.qubit + * // becomes (where %q maps to %q_final): + * qco.dealloc %q_final : !qco.qubit + * ``` + */ +struct ConvertQCDeallocOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& qubitMap = getState().qubitMap; + const auto& qcQubit = op.getQubit(); + + // Look up the latest QCO value for this QC qubit + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; + + // Create the dealloc operation + rewriter.replaceOpWithNewOp(op, qcoQubit); + + // Remove from state as qubit is no longer in use + qubitMap.erase(qcQubit); + + return success(); + } +}; + +/** + * @brief Converts qc.static to qco.static + * + * @details + * Static qubits represent references to hardware-mapped or fixed-position + * qubits identified by an index. This conversion creates the corresponding + * qco.static operation and establishes the mapping. + * + * Example transformation: + * ```mlir + * %q = qc.static 0 : !qc.qubit + * // becomes: + * %q0 = qco.static 0 : !qco.qubit + * ``` + */ +struct ConvertQCStaticOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& qubitMap = getState().qubitMap; + const auto& qcQubit = op.getQubit(); + + // Create new qco.static operation with the same index + auto qcoOp = rewriter.create(op.getLoc(), op.getIndex()); + + // Collect QCO qubit SSA value + const auto& qcoQubit = qcoOp.getQubit(); + + // Establish mapping from QC reference to QCO value + qubitMap[qcQubit] = qcoQubit; + + // Replace the old operation result with the new result + rewriter.replaceOp(op, qcoQubit); + + return success(); + } +}; + +/** + * @brief Converts qc.measure to qco.measure + * + * @details + * Measurement is a key operation where the semantic difference is visible: + * - QC: Measures in-place, returning only the classical bit + * - QCO: Consumes input qubit, returns both output qubit and classical bit + * + * The conversion looks up the latest QCO value for the QC qubit, + * performs the measurement, updates the mapping with the output qubit, + * and returns the classical bit result. + * + * Register metadata (name, size, index) for output recording is preserved + * during conversion. + * + * Example transformation: + * ```mlir + * %c = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 + * // becomes (where %q maps to %q_in): + * %q_out, %c = qco.measure("c", 2, 0) %q_in : !qco.qubit + * // state updated: %q now maps to %q_out + * ``` + */ +struct ConvertQCMeasureOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& qubitMap = getState().qubitMap; + const auto& qcQubit = op.getQubit(); + + // Get the latest QCO qubit value from the state map + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; + + // Create qco.measure (returns both output qubit and bit result) + auto qcoOp = rewriter.create( + op.getLoc(), qcoQubit, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); + + const auto& outQCOQubit = qcoOp.getQubitOut(); + const auto& newBit = qcoOp.getResult(); + + // Update mapping: the QC qubit now corresponds to the output qubit + qubitMap[qcQubit] = outQCOQubit; + + // Replace the QC operation's bit result with the QCO bit result + rewriter.replaceOp(op, newBit); + + return success(); + } +}; + +/** + * @brief Converts qc.reset to qco.reset + * + * @details + * Reset operations force a qubit to the |0⟩ state. The semantic difference: + * - QC: Resets in-place (no result value) + * - QCO: Consumes input qubit, returns reset output qubit + * + * The conversion looks up the latest QCO value, performs the reset, + * and updates the mapping with the output qubit. The QC operation + * is erased as it has no results to replace. + * + * Example transformation: + * ```mlir + * qc.reset %q : !qc.qubit + * // becomes (where %q maps to %q_in): + * %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit + * // state updated: %q now maps to %q_out + * ``` + */ +struct ConvertQCResetOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& qubitMap = getState().qubitMap; + const auto& qcQubit = op.getQubit(); + + // Get the latest QCO qubit value from the state map + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; + + // Create qco.reset (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit); + + // Update mapping: the QC qubit now corresponds to the reset output + qubitMap[qcQubit] = qcoOp.getQubitOut(); + + // Erase the old (it has no results to replace) + rewriter.eraseOp(op); + + return success(); + } +}; + +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM) \ + * ``` \ + * is converted to \ + * ```mlir \ + * qco.OP_NAME(%PARAM) \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertZeroTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetZeroParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetTwoParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit \ + * -> !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetThreeParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetZeroParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetTwoParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +/** + * @brief Converts qc.barrier to qco.barrier + * + * @par Example: + * ```mlir + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit + * ``` + * is converted to + * ```mlir + * %q0_out, %q1_out = qco.barrier %q0_in, %q1_in : !qco.qubit, !qco.qubit -> + * !qco.qubit, !qco.qubit + * ``` + */ +struct ConvertQCBarrierOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + auto& qubitMap = state.qubitMap; + + // Get QCO qubits from state map + const auto& qcQubits = op.getQubits(); + SmallVector qcoQubits; + qcoQubits.reserve(qcQubits.size()); + for (const auto& qcQubit : qcQubits) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubits.push_back(qubitMap[qcQubit]); + } + + // Create qco.barrier + auto qcoOp = rewriter.create(op.getLoc(), qcoQubits); + + // Update the state map + for (const auto& [qcQubit, qcoQubitOut] : + llvm::zip(qcQubits, qcoOp.getQubitsOut())) { + qubitMap[qcQubit] = qcoQubitOut; + } + + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Converts qc.ctrl to qco.ctrl + * + * @par Example: + * ```mlir + * qc.ctrl(%q0) { + * qc.x %q1 : !qc.qubit + * qc.yield + * } : !qc.qubit + * ``` + * is converted to + * ```mlir + * %controls_out, %targets_out = qco.ctrl(%q0_in) %q1_in { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + * ``` + */ +struct ConvertQCCtrlOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + auto& qubitMap = state.qubitMap; + + // Get QCO controls from state map + const auto& qcControls = op.getControls(); + SmallVector qcoControls; + qcoControls.reserve(qcControls.size()); + for (const auto& qcControl : qcControls) { + assert(qubitMap.contains(qcControl) && "QC qubit not found"); + qcoControls.push_back(qubitMap[qcControl]); + } + + // Get QCO targets from state map + const auto numTargets = op.getNumTargets(); + SmallVector qcoTargets; + qcoTargets.reserve(numTargets); + for (size_t i = 0; i < numTargets; ++i) { + const auto& qcTarget = op.getTarget(i); + assert(qubitMap.contains(qcTarget) && "QC qubit not found"); + const auto& qcoTarget = qubitMap[qcTarget]; + qcoTargets.push_back(qcoTarget); + } + + // Create qco.ctrl + auto qcoOp = + rewriter.create(op.getLoc(), qcoControls, qcoTargets); + + // Update the state map if this is a top-level CtrlOp + // Nested CtrlOps are managed via the targetsIn and targetsOut maps + if (state.inCtrlOp == 0) { + for (const auto& [qcControl, qcoControl] : + llvm::zip(qcControls, qcoOp.getControlsOut())) { + qubitMap[qcControl] = qcoControl; + } + const auto& targetsOut = qcoOp.getTargetsOut(); + for (size_t i = 0; i < numTargets; ++i) { + const auto& qcTarget = op.getTarget(i); + qubitMap[qcTarget] = targetsOut[i]; + } + } + + // Update modifier information + state.inCtrlOp++; + state.targetsIn.try_emplace(state.inCtrlOp, qcoTargets); + + // Clone body region from QC to QCO + auto& dstRegion = qcoOp.getBody(); + rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); + + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Converts qc.yield to qco.yield + * + * @par Example: + * ```mlir + * qc.yield + * ``` + * is converted to + * ```mlir + * qco.yield %targets + * ``` + */ +struct ConvertQCYieldOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::YieldOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + const auto& targets = state.targetsOut[state.inCtrlOp]; + rewriter.replaceOpWithNewOp(op, targets); + state.targetsOut.erase(state.inCtrlOp); + state.inCtrlOp--; + return success(); + } +}; + +/** + * @brief Pass implementation for QC-to-QCO conversion + * + * @details + * This pass converts QC dialect operations (reference + * semantics) to QCO dialect operations (value semantics). + * The conversion is essential for enabling optimization + * passes that rely on SSA form and explicit dataflow + * analysis. + * + * The pass operates in several phases: + * 1. Type conversion: !qc.qubit -> !qco.qubit + * 2. Operation conversion: Each QC op is converted to + * its QCO equivalent + * 3. State tracking: A LoweringState maintains qubit value + * mappings + * 4. Function/control-flow adaptation: Function signatures + * and control flow are updated to use QCO types + * + * The conversion maintains semantic equivalence while + * transforming the representation from imperative + * (mutation-based) to functional (SSA-based). + */ +struct QCToQCO final : impl::QCToQCOBase { + using QCToQCOBase::QCToQCOBase; + + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + // Create state object to track qubit value flow + LoweringState state; + + ConversionTarget target(*context); + RewritePatternSet patterns(context); + QCToQCOTypeConverter typeConverter(context); + + // Configure conversion target: QC illegal, QCO + // legal + target.addIllegalDialect(); + target.addLegalDialect(); + + // Register operation conversion patterns with state + // tracking + patterns.add(typeConverter, context, &state); + + // Conversion of qc types in func.func signatures + // Note: This currently has limitations with signature + // changes + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + + // Conversion of qc types in func.return + populateReturnOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + + // Conversion of qc types in func.call + populateCallOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::CallOp op) { return typeConverter.isLegal(op); }); + + // Conversion of qc types in control-flow ops (e.g., + // cf.br, cf.cond_br) + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + + // Apply the conversion + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mlir diff --git a/mlir/lib/Conversion/QCToQIR/CMakeLists.txt b/mlir/lib/Conversion/QCToQIR/CMakeLists.txt new file mode 100644 index 0000000000..a2715355f5 --- /dev/null +++ b/mlir/lib/Conversion/QCToQIR/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + QCToQIR + ${CONVERSION_SOURCES} + DEPENDS + QCToQIRIncGen + LINK_LIBS + PUBLIC + MLIRQIRUtils + MLIRLLVMDialect + MLIRQCDialect + MLIRArithDialect + MLIRFuncDialect + MLIRFuncToLLVM + MLIRReconcileUnrealizedCasts) diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp new file mode 100644 index 0000000000..01595932f8 --- /dev/null +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -0,0 +1,1270 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QCToQIR/QCToQIR.h" + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { + +using namespace qc; +using namespace qir; + +#define GEN_PASS_DEF_QCTOQIR +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" + +namespace { + +/** + * @brief State object for tracking lowering information during QIR conversion + * + * @details + * This struct maintains state during the conversion of QC dialect + * operations to QIR (Quantum Intermediate Representation). It tracks: + * - Qubit and result counts for QIR metadata + * - Pointer value caching for reuse + * - Whether dynamic memory management is needed + * - Sequence of measurements for output recording + */ +struct LoweringState : QIRMetadata { + /// Map from register name to register start index + DenseMap registerStartIndexMap; + + /// Map from index to pointer value for reuse + DenseMap ptrMap; + + /// Map from (register_name, register_index) to result pointer + /// This allows caching result pointers for measurements with register info + DenseMap, Value> registerResultMap; + + /// Modifier information + int64_t inCtrlOp = 0; + DenseMap> posCtrls; +}; + +/** + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which tracks qubit/result counts and caches values across multiple + * pattern applications. + * + * @tparam OpType The operation type to convert + */ +template +class StatefulOpConversionPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, MLIRContext* ctx, + LoweringState* state) + : OpConversionPattern(typeConverter, ctx), state_(state) {} + + /// Returns the shared lowering state object + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; + +} // namespace + +/** + * @brief Helper to convert a QC operation to a LLVM CallOp + * + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOpAdaptorType The OpAdaptor type of the QC operation + * @param op The QC operation instance to convert + * @param adaptor The OpAdaptor of the QC operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @param numTargets The number of targets + * @param numParams The number of parameters + * @return LogicalResult Success or failure of the conversion + */ +template +static LogicalResult +convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, MLIRContext* ctx, + LoweringState& state, StringRef fnName, + size_t numTargets, size_t numParams) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numParams + numCtrls + numTargets); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + const auto floatType = Float64Type::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + for (size_t i = 0; i < numTargets; ++i) { + argumentTypes.push_back(ptrType); + } + // Add parameter types + for (size_t i = 0; i < numParams; ++i) { + argumentTypes.push_back(floatType); + } + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numParams + numCtrls + numTargets); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + +/** + * @brief Type converter for lowering QC dialect types to LLVM types + * + * @details + * Converts QC dialect types to their LLVM equivalents for QIR emission. + * + * Type conversions: + * - `!qc.qubit` -> `!llvm.ptr` (opaque pointer to qubit in QIR) + */ +struct QCToQIRTypeConverter final : LLVMTypeConverter { + explicit QCToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { + // Convert QubitType to LLVM pointer (QIR uses opaque pointers for qubits) + addConversion( + [ctx](QubitType /*type*/) { return LLVM::LLVMPointerType::get(ctx); }); + } +}; + +namespace { + +/** + * @brief Converts qc.alloc operation to static QIR qubit allocations + * + * @details + * QIR 2.0 does not support dynamic qubit allocation. Therefore, qc.alloc + * operations are converted to static qubit references using inttoptr with a + * constant index. + * + * Register metadata (register_name, register_size, register_index) is used to + * provide a reasonable guess for a static qubit index that is still free. + * + * @par Example: + * ```mlir + * %q = qc.alloc : !qc.qubit + * ``` + * becomes: + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * ``` + */ +struct ConvertQCAllocQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + const auto numQubits = static_cast(state.numQubits); + auto& ptrMap = state.ptrMap; + auto& registerMap = state.registerStartIndexMap; + + // Get or create pointer value + if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { + const auto registerName = op.getRegisterName().value(); + const auto registerSize = + static_cast(op.getRegisterSize().value()); + const auto registerIndex = + static_cast(op.getRegisterIndex().value()); + + if (const auto it = registerMap.find(registerName); + it != registerMap.end()) { + // Register is already tracked + // The pointer was created by the step below + const auto globalIndex = it->second + registerIndex; + if (!ptrMap.contains(globalIndex)) { + return op.emitError("Pointer not found"); + } + rewriter.replaceOp(op, ptrMap.at(globalIndex)); + return success(); + } + + // Allocate the entire register as static qubits + registerMap[registerName] = numQubits; + SmallVector pointers; + pointers.reserve(registerSize); + for (int64_t i = 0; i < registerSize; ++i) { + Value val{}; + if (const auto it = ptrMap.find(numQubits + i); it != ptrMap.end()) { + val = it->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numQubits + i); + ptrMap[numQubits + i] = val; + } + pointers.push_back(val); + } + rewriter.replaceOp(op, pointers[registerIndex]); + state.numQubits += registerSize; + return success(); + } + + // no register info, check if ptr has already been allocated (as a Result) + Value val{}; + if (const auto it = ptrMap.find(numQubits); it != ptrMap.end()) { + val = it->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numQubits); + ptrMap[numQubits] = val; + } + rewriter.replaceOp(op, val); + state.numQubits++; + return success(); + } +}; + +/** + * @brief Erases qc.dealloc operations + * + * @details + * Since QIR 2.0 does not support dynamic qubit allocation, dynamic allocations + * are converted to static allocations. Therefore, deallocation operations + * become no-ops and are simply removed from the IR. + * + * @par Example: + * ```mlir + * qc.dealloc %q : !qc.qubit + * ``` + * becomes: + * ```mlir + * // (removed) + * ``` + */ +struct ConvertQCDeallocQIR final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(DeallocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Converts qc.static operation to QIR inttoptr + * + * @details + * Converts a static qubit reference to an LLVM pointer by creating a constant + * with the qubit index and converting it to a pointer. The pointer is cached + * in the lowering state for reuse. + * + * @par Example: + * ```mlir + * %q0 = qc.static 0 : !qc.qubit + * ``` + * becomes: + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * ``` + */ +struct ConvertQCStaticQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto index = static_cast(op.getIndex()); + auto& state = getState(); + // Get or create a pointer to the qubit + Value val{}; + if (const auto it = state.ptrMap.find(index); it != state.ptrMap.end()) { + // Reuse existing pointer + val = it->second; + } else { + // Create and cache for reuse + val = createPointerFromIndex(rewriter, op.getLoc(), index); + state.ptrMap.try_emplace(index, val); + } + rewriter.replaceOp(op, val); + + // Track maximum qubit index + if (std::cmp_greater_equal(index, state.numQubits)) { + state.numQubits = index + 1; + } + + return success(); + } +}; + +/** + * @brief Converts qc.measure operation to QIR measurement + * + * @details + * Converts qubit measurement to a QIR call to `__quantum__qis__mz__body`. + * Unlike the previous implementation, this does NOT immediately record output. + * Instead, it tracks measurements in the lowering state for deferred output + * recording in a separate output block, as required by the QIR Base Profile. + * + * For measurements with register information, the result pointer is mapped + * to (register_name, register_index) for later retrieval. For measurements + * without register information, a sequential result pointer is assigned. + * + * @par Example (with register): + * ```mlir + * %result = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 + * ``` + * becomes: + * ```mlir + * %c0_i64 = llvm.mlir.constant(0 : i64) : i64 + * %result_ptr = llvm.inttoptr %c0_i64 : i64 to !llvm.ptr + * llvm.call @__quantum__qis__mz__body(%q, %result_ptr) : (!llvm.ptr, !llvm.ptr) + * -> () + * ``` + */ +struct ConvertQCMeasureQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + auto& state = getState(); + const auto numResults = static_cast(state.numResults); + auto& ptrMap = state.ptrMap; + auto& registerResultMap = state.registerResultMap; + + // Get or create result pointer value + Value resultValue; + if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { + const auto registerName = op.getRegisterName().value(); + const auto registerSize = + static_cast(op.getRegisterSize().value()); + const auto registerIndex = + static_cast(op.getRegisterIndex().value()); + const auto key = std::make_pair(registerName, registerIndex); + + if (const auto it = registerResultMap.find(key); + it != registerResultMap.end()) { + resultValue = it->second; + } else { + // Allocate the entire register as static results + for (int64_t i = 0; i < registerSize; ++i) { + Value val{}; + if (const auto it = ptrMap.find(numResults + i); it != ptrMap.end()) { + val = it->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numResults + i); + ptrMap[numResults + i] = val; + } + registerResultMap.try_emplace({registerName, i}, val); + } + state.numResults += registerSize; + resultValue = registerResultMap.at(key); + } + } else { + // Choose a safe default register name + StringRef defaultRegName = "c"; + if (llvm::any_of(registerResultMap, [](const auto& entry) { + return entry.first.first == "c"; + })) { + defaultRegName = "__unnamed__"; + } + // No register info, check if ptr has already been allocated (as a Qubit) + if (const auto it = ptrMap.find(numResults); it != ptrMap.end()) { + resultValue = it->second; + } else { + resultValue = createPointerFromIndex(rewriter, op.getLoc(), numResults); + ptrMap[numResults] = resultValue; + } + registerResultMap.insert({{defaultRegName, numResults}, resultValue}); + state.numResults++; + } + + // Declare QIR function + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, fnSignature); + + // Create CallOp and replace qc.measure with result pointer + rewriter.create(op.getLoc(), fnDecl, + ValueRange{adaptor.getQubit(), resultValue}); + rewriter.replaceOp(op, resultValue); + return success(); + } +}; + +/** + * @brief Converts qc.reset operation to QIR reset + * + * @details + * Converts qubit reset to a call to the QIR __quantum__qis__reset__body + * function, which resets a qubit to the |0⟩ state. + * + * @par Example: + * ```mlir + * qc.reset %q : !qc.qubit + * ``` + * becomes: + * ```mlir + * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQCResetQIR final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Declare QIR function + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, fnSignature); + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + +// GPhaseOp + +/** + * @brief Converts qc.gphase to QIR gphase + * + * @par Example: + * ```mlir + * qc.gphase(%theta) + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__gphase__body(%theta) : (f64) -> () + * ``` + */ +struct ConvertQCGPhaseOpQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(GPhaseOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + if (state.inCtrlOp != 0) { + return op.emitError("Controlled GPhaseOps cannot be converted to QIR"); + } + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), state, + QIR_GPHASE, 0, 1); + } +}; + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q) : (!llvm.ptr) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 0); \ + } \ + }; + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, I, id, i) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, X, x, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, Y, y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, Z, z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, H, h, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, S, s, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, SDG, sdg, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, T, t, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, TDG, tdg, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, SX, sx, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL(%PARAM) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM) : (!llvm.ptr, f64) \ + * -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 1); \ + } \ + }; + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, RX, rx, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, RY, ry, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, RZ, rz, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 2); \ + } \ + }; + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, R, r, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME_BIG, \ + OP_NAME_SMALL, QIR_NAME) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM1, %PARAM2, %PARAM3) \ + * : (!llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp( \ + op, adaptor, rewriter, getContext(), state, fnName, 1, 3); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL %q1, %q2 : !qc.qubit, !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 0); \ + } \ + }; + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, SWAP, swap, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, ISWAP, iswap, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, DCX, dcx, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 1); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, RXX, rxx, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, RYY, ryy, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, RZX, rzx, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ + * !qc.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQC##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 2); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, XXPLUSYY, xx_plus_yy, xx_plus_yy, + theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, XXMINUSYY, xx_minus_yy, + xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +/** + * @brief Erases qc.barrier operation, as it is a no-op in QIR + */ +struct ConvertQCBarrierQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(BarrierOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Inlines qc.ctrl region removes the operation + */ +struct ConvertQCCtrlQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(CtrlOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Update modifier information + auto& state = getState(); + state.inCtrlOp++; + const SmallVector posCtrls(adaptor.getControls().begin(), + adaptor.getControls().end()); + state.posCtrls[state.inCtrlOp] = posCtrls; + + // Inline region and remove operation + rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), + op->getIterator()); + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Erases qc.yield operation + */ +struct ConvertQCYieldQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(YieldOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.eraseOp(op); + return success(); + } +}; + +} // namespace + +/** + * @brief Pass for converting QC dialect operations to QIR + * + * @details + * This pass converts QC dialect quantum operations to QIR (Quantum + * Intermediate Representation) by lowering them to LLVM dialect operations + * that call QIR runtime functions. + * + * Conversion stages: + * 1. Convert func dialect to LLVM + * 2. Ensure proper block structure for QIR base profile and add + * initialization + * 3. Convert QC operations to QIR calls + * 4. Set QIR metadata attributes + * 5. Convert arith and cf dialects to LLVM + * 6. Reconcile unrealized casts + * + * @pre + * The input entry function must consist of a single block. The pass will + * restructure it into four blocks. Multi-block input functions are currently + * not supported. + */ +struct QCToQIR final : impl::QCToQIRBase { + using QCToQIRBase::QCToQIRBase; + + /** + * @brief Ensures proper block structure for QIR base profile + * + * @details + * The QIR base profile requires a specific 4-block structure: + * 1. **Entry block**: Contains constant operations and initialization + * 2. **Body block**: Contains reversible quantum operations (gates) + * 3. **Measurements block**: Contains irreversible operations (measure, + * reset, dealloc) + * 4. **Output block**: Contains output recording calls + * + * Blocks are connected with unconditional jumps (entry, body, measurements, + * output). This structure ensures proper QIR Base Profile semantics. + * + * If the function already has multiple blocks, this function does nothing. + * + * @param main The main LLVM function to restructure + */ + static void ensureBlocks(LLVM::LLVMFuncOp& main) { + // Return if there are already multiple blocks + if (main.getBlocks().size() > 1) { + return; + } + + // Get the existing block + auto* bodyBlock = &main.front(); + OpBuilder builder(main.getBody()); + + // Create the required blocks + auto* entryBlock = builder.createBlock(&main.getBody()); + // Move the entry block before the body block + main.getBlocks().splice(Region::iterator(bodyBlock), main.getBlocks(), + entryBlock); + Block* measurementsBlock = builder.createBlock(&main.getBody()); + Block* outputBlock = builder.createBlock(&main.getBody()); + + auto& bodyBlockOps = bodyBlock->getOperations(); + auto& outputBlockOps = outputBlock->getOperations(); + auto& measurementsBlockOps = measurementsBlock->getOperations(); + + // Move operations to appropriate blocks + for (auto it = bodyBlock->begin(); it != bodyBlock->end();) { + // Ensure iterator remains valid after potential move + if (auto& op = *it++; + isa(op) || isa(op) || isa(op)) { + // Move irreversible quantum operations to measurements block + measurementsBlockOps.splice(measurementsBlock->end(), bodyBlockOps, + Block::iterator(op)); + } else if (isa(op)) { + // Move return to output block + outputBlockOps.splice(outputBlock->end(), bodyBlockOps, + Block::iterator(op)); + } else if (op.hasTrait()) { + // Move constant like operations to the entry block + entryBlock->getOperations().splice(entryBlock->end(), bodyBlockOps, + Block::iterator(op)); + } + // All other operations (gates, etc.) stay in body block + } + + // Add unconditional jumps between blocks + builder.setInsertionPointToEnd(entryBlock); + builder.create(main->getLoc(), bodyBlock); + + builder.setInsertionPointToEnd(bodyBlock); + builder.create(main->getLoc(), measurementsBlock); + + builder.setInsertionPointToEnd(measurementsBlock); + builder.create(main->getLoc(), outputBlock); + } + + /** + * @brief Adds QIR initialization call to the entry block + * + * @details + * Inserts a call to `__quantum__rt__initialize` at the end of the entry + * block (before the jump to main block). This QIR runtime function + * initializes the quantum execution environment and takes a null pointer as + * argument. + * + * @param main The main LLVM function + * @param ctx The MLIR context + */ + static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx) { + auto moduleOp = main->getParentOfType(); + auto& firstBlock = *(main.getBlocks().begin()); + OpBuilder builder(main.getBody()); + + // Create a zero (null) pointer for the initialize call + builder.setInsertionPointToStart(&firstBlock); + auto zeroOp = builder.create(main->getLoc(), + LLVM::LLVMPointerType::get(ctx)); + + // Insert the initialize call before the jump to main block + const auto insertPoint = std::prev(firstBlock.getOperations().end(), 1); + builder.setInsertionPoint(&*insertPoint); + + // Get or create the initialize function declaration + auto* fnDecl = SymbolTable::lookupNearestSymbolFrom( + main, builder.getStringAttr(QIR_INITIALIZE)); + if (fnDecl == nullptr) { + const PatternRewriter::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(moduleOp.getBody()); + auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + fnDecl = builder.create(main->getLoc(), QIR_INITIALIZE, + fnSignature); + } + + // Create the initialization call + builder.create(main->getLoc(), cast(fnDecl), + ValueRange{zeroOp->getResult(0)}); + } + + /** + * @brief Adds output recording calls to the output block + * + * @details + * Generates output recording calls in the output block based on the + * measurements tracked during conversion. Follows the QIR Base Profile + * specification for labeled output schema. + * + * For each classical register, creates: + * 1. An array_record_output call with the register size and label + * 2. Individual result_record_output calls for each measurement in the + * register + * + * Labels follow the format: "{registerName}{resultIndex}r" + * - registerName: Name of the classical register (e.g., "c") + * - resultIndex: Index within the array + * - 'r' suffix: Indicates this is a result record + * + * Example output: + * ``` + * @0 = internal constant [3 x i8] c"c\00" + * @1 = internal constant [5 x i8] c"c0r\00" + * @2 = internal constant [5 x i8] c"c1r\00" + * call void @__quantum__rt__array_record_output(i64 2, ptr @0) + * call void @__quantum__rt__result_record_output(ptr %result0, ptr @1) + * call void @__quantum__rt__result_record_output(ptr %result1, ptr @2) + * ``` + * + * Any output recording calls that are not part of registers (i.e., + * measurements without register info) are grouped under a default label + * "c" and recorded similarly. + * + * @param main The main LLVM function + * @param ctx The MLIR context + * @param state The lowering state containing measurement information + */ + static void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, + LoweringState* state) { + if (state->registerResultMap.empty()) { + return; // No measurements to record + } + + OpBuilder builder(ctx); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + + // Find the output block + auto& outputBlock = main.getBlocks().back(); + + // Insert before the branch to output block + builder.setInsertionPoint(&outputBlock.back()); + + // Group measurements by register + llvm::StringMap>> registerGroups; + for (const auto& [key, resultPtr] : state->registerResultMap) { + const auto& [registerName, registerIndex] = key; + registerGroups[registerName].emplace_back(registerIndex, resultPtr); + } + + // Sort registers by name for deterministic output + SmallVector>>> + sortedRegisters; + for (auto& [name, measurements] : registerGroups) { + sortedRegisters.emplace_back(name, std::move(measurements)); + } + llvm::sort(sortedRegisters, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // create function declarations for output recording + const auto arrayRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {builder.getI64Type(), ptrType}); + const auto arrayRecordDecl = getOrCreateFunctionDeclaration( + builder, main, QIR_ARRAY_RECORD_OUTPUT, arrayRecordSig); + + const auto resultRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); + const auto resultRecordDecl = getOrCreateFunctionDeclaration( + builder, main, QIR_RECORD_OUTPUT, resultRecordSig); + + // Generate output recording for each register + for (auto& [registerName, measurements] : sortedRegisters) { + // Sort measurements by register index + llvm::sort(measurements, [](const auto& a, const auto& b) { + return a.first < b.first; + }); + + const auto arraySize = measurements.size(); + auto arrayLabelOp = createResultLabel(builder, main, registerName); + auto arraySizeConst = builder.create( + main->getLoc(), + builder.getI64IntegerAttr(static_cast(arraySize))); + + builder.create( + main->getLoc(), arrayRecordDecl, + ValueRange{arraySizeConst.getResult(), arrayLabelOp.getResult()}); + + // Create result_record_output calls for each measurement + for (auto [regIdx, resultPtr] : measurements) { + // Create label for result: "{arrayCounter+1+i}_{registerName}{i}r" + const std::string resultLabel = + registerName.str() + std::to_string(regIdx) + "r"; + auto resultLabelOp = createResultLabel(builder, main, resultLabel); + + builder.create( + main->getLoc(), resultRecordDecl, + ValueRange{resultPtr, resultLabelOp.getResult()}); + } + } + } + + /** + * @brief Executes the QC to QIR conversion pass + * + * @details + * Performs the conversion in six stages: + * + * **Stage 1: Func to LLVM** + * Convert func dialect operations (main function) to LLVM dialect + * equivalents. + * + * **Stage 2: Block structure and initialization** + * Create proper 4-block structure for QIR base profile (entry, main, + * irreversible, output) and insert the `__quantum__rt__initialize` call + * in the entry block. + * + * **Stage 3: QC to LLVM** + * Convert QC dialect operations to QIR calls and add output recording to + * the output block. + * + * **Stage 4: QIR attributes** + * Add QIR base profile metadata to the main function, including + * qubit/result counts and version information. + * + * **Stage 5: Standard dialects to LLVM** + * Convert arith and control flow dialects to LLVM (for index arithmetic and + * function control flow). + * + * **Stage 6: Reconcile casts** + * Clean up any unrealized cast operations introduced during type + * conversion. + */ + void runOnOperation() override { + MLIRContext* ctx = &getContext(); + auto* moduleOp = getOperation(); + ConversionTarget target(*ctx); + QCToQIRTypeConverter typeConverter(ctx); + + target.addLegalDialect(); + + // Stage 1: Convert func dialect to LLVM + { + RewritePatternSet funcPatterns(ctx); + target.addIllegalDialect(); + populateFuncToLLVMConversionPatterns(typeConverter, funcPatterns); + + if (applyPartialConversion(moduleOp, target, std::move(funcPatterns)) + .failed()) { + signalPassFailure(); + return; + } + } + + // Stage 2: Ensure proper block structure and add initialization + auto main = getMainFunction(moduleOp); + if (!main) { + moduleOp->emitError("No main function with entry_point attribute found"); + signalPassFailure(); + return; + } + + ensureBlocks(main); + addInitialize(main, ctx); + + LoweringState state; + + // Stage 3: Convert QC dialect to LLVM (QIR calls) + { + RewritePatternSet qcPatterns(ctx); + target.addIllegalDialect(); + + // Add conversion patterns for QC operations + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + + if (applyPartialConversion(moduleOp, target, std::move(qcPatterns)) + .failed()) { + signalPassFailure(); + return; + } + + addOutputRecording(main, ctx, &state); + } + + // Stage 4: Set QIR metadata attributes + setQIRAttributes(main, state); + + // Stage 5: Convert standard dialects to LLVM + { + RewritePatternSet stdPatterns(ctx); + target.addIllegalDialect(); + target.addIllegalDialect(); + + cf::populateControlFlowToLLVMConversionPatterns(typeConverter, + stdPatterns); + arith::populateArithToLLVMConversionPatterns(typeConverter, stdPatterns); + + if (applyPartialConversion(moduleOp, target, std::move(stdPatterns)) + .failed()) { + signalPassFailure(); + return; + } + } + + // Stage 6: Reconcile unrealized casts + PassManager passManager(ctx); + passManager.addPass(createReconcileUnrealizedCastsPass()); + if (passManager.run(moduleOp).failed()) { + signalPassFailure(); + } + } +}; + +} // namespace mlir diff --git a/mlir/lib/Dialect/CMakeLists.txt b/mlir/lib/Dialect/CMakeLists.txt index 1a85073274..ce032ab729 100644 --- a/mlir/lib/Dialect/CMakeLists.txt +++ b/mlir/lib/Dialect/CMakeLists.txt @@ -8,3 +8,6 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) +add_subdirectory(QCO) +add_subdirectory(QIR) +add_subdirectory(QC) diff --git a/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp b/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp index 4d32a15f4a..a8f553744c 100644 --- a/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp +++ b/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp @@ -71,19 +71,19 @@ void mqt::ir::opt::MQTOptDialect::initialize() { #include "mlir/Dialect/MQTOpt/IR/MQTOptOps.cpp.inc" namespace mqt::ir::opt { -namespace { + /** * @brief Prints the given list of types as a comma-separated list * * @param printer The printer to use. * @param types The types to print. **/ -void printCommaSeparated(mlir::OpAsmPrinter& printer, mlir::TypeRange types) { +static void printCommaSeparated(mlir::OpAsmPrinter& printer, + mlir::TypeRange types) { llvm::interleaveComma(llvm::make_range(types.begin(), types.end()), printer.getStream(), [&printer](mlir::Type t) { printer.printType(t); }); } -} // namespace mlir::ParseResult parseOptOutputTypes(mlir::OpAsmParser& parser, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp index 06c64a6b1f..3387ec26b3 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp @@ -39,24 +39,23 @@ namespace mqt::ir::opt { -namespace { -bool isQubitType(const mlir::MemRefType type) { +static bool isQubitType(mlir::MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(mlir::memref::LoadOp op) { +static bool isQubitType(mlir::memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(mlir::memref::StoreOp op) { +static bool isQubitType(mlir::memref::StoreOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isSupportedMemRefOp(mlir::Operation* op) { +static bool isSupportedMemRefOp(mlir::Operation* op) { if (auto loadOp = llvm::dyn_cast(op)) { return isQubitType(loadOp); } @@ -65,7 +64,6 @@ bool isSupportedMemRefOp(mlir::Operation* op) { } return false; } -} // namespace /// Analysis pattern that filters out all quantum operations from a given /// program and creates a quantum computation from them. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 8c0e46b503..05809e188e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -80,9 +80,10 @@ class SynchronizationMap { /// @brief Maps operations to ref counts. mlir::DenseMap refCount; }; +} // namespace -mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, - Layer& opLayer) { +static mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, + Layer& opLayer) { assert(wires.size() == 2 && "expected two wires"); auto [it0, index0] = wires[0]; @@ -130,7 +131,6 @@ mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, return {Wire(it0, index0), Wire(it1, index1)}; } -} // namespace LayeredUnit LayeredUnit::fromEntryPointFunction(mlir::func::FuncOp func, const std::size_t nqubits) { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index c82a55a56f..d6de4b3d55 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -46,11 +46,12 @@ namespace mqt::ir::opt { +using namespace mlir; + #define GEN_PASS_DEF_PLACEMENTPASSSC #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" namespace { -using namespace mlir; /** * @brief 'For' pushes once onto the stack, hence the parent is at depth one. @@ -131,12 +132,14 @@ struct PlacementContext { LayoutStack stack{}; }; +} // namespace + /** * @brief Adds the necessary hardware qubits for entry_point functions and * prepares the stack for the qubit placement in the function body. */ -WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { assert(ctx.stack.empty() && "handleFunc: stack must be empty"); rewriter.setInsertionPointToStart(&op.getBody().front()); @@ -172,7 +175,7 @@ WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, * @brief Indicates the end of a region defined by a function. Consequently, pop * the region's state from the stack. */ -WalkResult handleReturn(PlacementContext& ctx) { +static WalkResult handleReturn(PlacementContext& ctx) { ctx.stack.pop(); return WalkResult::advance(); } @@ -184,8 +187,8 @@ WalkResult handleReturn(PlacementContext& ctx) { * Prepares the stack for the placement of the loop body by adding a copy of the * current state to the stack. Forwards the results in the parent state. */ -WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t nargs = op.getBody()->getNumArguments(); const std::size_t nresults = op->getNumResults(); @@ -231,8 +234,8 @@ WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, * a copy of the current state to the stack for each branch. Forwards the * results in the parent state. */ -WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t nresults = op->getNumResults(); /// Construct new result types. @@ -277,8 +280,8 @@ WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, * @brief Indicates the end of a region defined by a branching op. Consequently, * we pop the region's state from the stack. */ -WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { if (!isa(op->getParentOp()) && !isa(op->getParentOp())) { return WalkResult::skip(); @@ -296,8 +299,8 @@ WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, * @brief Retrieve free qubit from pool and replace the allocated qubit with it. * Reset the qubit if it has already been allocated before. */ -WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { if (ctx.pool.empty()) { return op.emitOpError( "requires one too many qubits for the targeted architecture"); @@ -330,8 +333,8 @@ WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, /** * @brief Release hardware qubit and erase dealloc operation. */ -WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t index = ctx.stack.top().lookupHardwareIndex(op.getQubit()); ctx.pool.push_back(index); rewriter.eraseOp(op); @@ -341,7 +344,7 @@ WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, /** * @brief Update layout. */ -WalkResult handleReset(ResetOp op, PlacementContext& ctx) { +static WalkResult handleReset(ResetOp op, PlacementContext& ctx) { ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -349,7 +352,7 @@ WalkResult handleReset(ResetOp op, PlacementContext& ctx) { /** * @brief Update layout. */ -WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { +static WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -357,7 +360,7 @@ WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { /** * @brief Update layout. */ -WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { +static WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { for (const auto [in, out] : llvm::zip(op.getAllInQubits(), op.getAllOutQubits())) { ctx.stack.top().remapQubitValue(in, out); @@ -366,8 +369,8 @@ WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { return WalkResult::advance(); } -LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, - PlacementContext& ctx) { +static LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, + PlacementContext& ctx) { PatternRewriter rewriter(mlirCtx); /// Prepare work-list. @@ -431,6 +434,8 @@ LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, return success(); } +namespace { + /** * @brief This pass maps program qubits to hardware qubits on superconducting * quantum devices using initial placement strategies. @@ -489,5 +494,7 @@ struct PlacementPassSC final : impl::PlacementPassSCBase { return success(); } }; + } // namespace + } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp index 8bc5b76e53..49548caf1f 100644 --- a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp @@ -53,6 +53,8 @@ struct QregInfo { using BitMemInfo = std::pair; // (memref, localIdx) using BitIndexVec = llvm::SmallVector; +} // namespace + /** * @brief Allocates a quantum register in the MLIR module. * @@ -61,8 +63,9 @@ using BitIndexVec = llvm::SmallVector; * @param numQubits The number of qubits to allocate in the register * @return mlir::Value The allocated quantum register value */ -mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, - const std::size_t numQubits) { +static mlir::Value allocateQreg(mlir::OpBuilder& builder, + mlir::MLIRContext* context, + const std::size_t numQubits) { const auto& qubitType = mqt::ir::ref::QubitType::get(context); auto memRefType = mlir::MemRefType::get({static_cast(numQubits)}, qubitType); @@ -79,9 +82,9 @@ mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, * @param numQubits The number of qubits to extract * @return llvm::SmallVector Vector of extracted qubit values */ -llvm::SmallVector extractQubits(mlir::OpBuilder& builder, - mlir::Value qreg, - const std::size_t numQubits) { +static llvm::SmallVector +extractQubits(mlir::OpBuilder& builder, mlir::Value qreg, + const std::size_t numQubits) { llvm::SmallVector qubits; qubits.reserve(numQubits); @@ -107,7 +110,7 @@ llvm::SmallVector extractQubits(mlir::OpBuilder& builder, * @return llvm::SmallVector Vector containing information about all * quantum registers */ -llvm::SmallVector +static llvm::SmallVector getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting @@ -148,7 +151,7 @@ getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, * @param qregs Vector containing information about all quantum registers * @return llvm::SmallVector Sorted vector of qubit values */ -llvm::SmallVector +static llvm::SmallVector getQubits(const qc::QuantumComputation& quantumComputation, llvm::SmallVector& qregs) { llvm::SmallVector flatQubits; @@ -171,7 +174,7 @@ getQubits(const qc::QuantumComputation& quantumComputation, * @param builder The MLIR OpBuilder used to create operations * @param qreg The quantum register to deallocate */ -void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { +static void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { builder.create(builder.getUnknownLoc(), qreg); } @@ -182,7 +185,7 @@ void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { * @param numBits The number of bits to allocate in the register * @return mlir::Value The allocated classical register value */ -mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { +static mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { auto memRefType = mlir::MemRefType::get({numBits}, builder.getI1Type()); auto memref = builder.create(builder.getUnknownLoc(), memRefType); @@ -196,8 +199,8 @@ mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { * @param numBits The number of bits to allocate in the register * @return mlir::Value The allocated classical register value */ -BitIndexVec getBitMap(mlir::OpBuilder& builder, - const qc::QuantumComputation& quantumComputation) { +static BitIndexVec getBitMap(mlir::OpBuilder& builder, + const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting llvm::SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); @@ -234,8 +237,9 @@ BitIndexVec getBitMap(mlir::OpBuilder& builder, * @param qubits The qubits of the quantum register */ template -void addUnitaryOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { +static void addUnitaryOp(mlir::OpBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits) { // Define operation parameters mlir::DenseF64ArrayAttr staticParamsAttr = nullptr; if (const auto& parameters = operation.getParameter(); !parameters.empty()) { @@ -280,9 +284,10 @@ void addUnitaryOp(mlir::OpBuilder& builder, const qc::Operation& operation, * @param bitMap The mapping from global classical bit index to (memref, * localIdx) */ -void addMeasureOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static void addMeasureOp(mlir::OpBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -308,8 +313,8 @@ void addMeasureOp(mlir::OpBuilder& builder, const qc::Operation& operation, * @param operation The reset operation to add * @param qubits The qubits of the quantum register */ -void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { +static void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const mlir::Value inQubit = qubits[target]; builder.create(builder.getUnknownLoc(), inQubit); @@ -317,10 +322,10 @@ void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, } // Forward declaration -llvm::LogicalResult addOperation(mlir::OpBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap); +static llvm::LogicalResult +addOperation(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap); /** * @brief Adds the operation(s) in a block to the MLIR module. @@ -332,10 +337,10 @@ llvm::LogicalResult addOperation(mlir::OpBuilder& builder, * localIdx) * @return llvm::LogicalResult whether the adding of the blockOps was successful */ -llvm::LogicalResult addBlockOps(mlir::OpBuilder& builder, - const qc::Operation* operationInBlock, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addBlockOps(mlir::OpBuilder& builder, const qc::Operation* operationInBlock, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { if (operationInBlock->isCompoundOperation()) { for (const auto& operation : dynamic_cast(*operationInBlock)) { @@ -358,8 +363,8 @@ llvm::LogicalResult addBlockOps(mlir::OpBuilder& builder, * @param bits The bits of the classical register * @return mlir::Value The integer value of the register */ -mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, - const mlir::Value bits) { +static mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, + const mlir::Value bits) { const auto loc = builder.getUnknownLoc(); // Extract length (assumed 1-D static memref) @@ -412,10 +417,10 @@ mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addIfElseOp(mlir::OpBuilder& builder, - const qc::Operation& op, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addIfElseOp(mlir::OpBuilder& builder, const qc::Operation& op, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto loc = builder.getUnknownLoc(); const auto& ifElse = dynamic_cast(op); @@ -521,10 +526,10 @@ llvm::LogicalResult addIfElseOp(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addOperation(mlir::OpBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addOperation(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { switch (operation.getType()) { ADD_OP_CASE(Barrier) ADD_OP_CASE(I) @@ -584,7 +589,7 @@ llvm::LogicalResult addOperation(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addOperations( +static llvm::LogicalResult addOperations( mlir::OpBuilder& builder, const qc::QuantumComputation& quantumComputation, const llvm::SmallVector& qubits, const BitIndexVec& bitMap) { for (const auto& operation : quantumComputation) { @@ -595,7 +600,6 @@ llvm::LogicalResult addOperations( } return llvm::success(); } -} // namespace /** * @brief Translates a QuantumComputation to an MLIR module with MQTRef diff --git a/mlir/lib/Dialect/QC/Builder/CMakeLists.txt b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt new file mode 100644 index 0000000000..ddd7989417 --- /dev/null +++ b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRQCProgramBuilder + QCProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRArithDialect + MLIRFuncDialect + MLIRSCFDialect + MLIRQCDialect) + +# collect header files +file(GLOB_RECURSE BUILDER_HEADERS_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRQCProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp new file mode 100644 index 0000000000..f9139835fe --- /dev/null +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qc { + +QCProgramBuilder::QCProgramBuilder(MLIRContext* context) + : OpBuilder(context), ctx(context), loc(getUnknownLoc()), + module(ModuleOp::create(loc)) { + ctx->loadDialect(); +} + +void QCProgramBuilder::initialize() { + // Set insertion point to the module body + setInsertionPointToStart(module.getBody()); + + // Create main function as entry point + auto funcType = getFunctionType({}, {getI64Type()}); + auto mainFunc = func::FuncOp::create(*this, loc, "main", funcType); + + // Add entry_point attribute to identify the main function + auto entryPointAttr = getStringAttr("entry_point"); + mainFunc->setAttr("passthrough", getArrayAttr({entryPointAttr})); + + // Create entry block and set insertion point + auto& entryBlock = mainFunc.getBody().emplaceBlock(); + setInsertionPointToStart(&entryBlock); +} + +Value QCProgramBuilder::allocQubit() { + checkFinalized(); + + // Create the AllocOp without register metadata + auto allocOp = AllocOp::create(*this, loc); + const auto qubit = allocOp.getResult(); + + // Track the allocated qubit for automatic deallocation + allocatedQubits.insert(qubit); + + return qubit; +} + +Value QCProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + + // Create the StaticOp with the given index + auto indexAttr = getI64IntegerAttr(index); + auto staticOp = StaticOp::create(*this, loc, indexAttr); + return staticOp.getQubit(); +} + +llvm::SmallVector +QCProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + // Allocate a sequence of qubits with register metadata + llvm::SmallVector qubits; + qubits.reserve(size); + + auto nameAttr = getStringAttr(name); + auto sizeAttr = getI64IntegerAttr(size); + + for (int64_t i = 0; i < size; ++i) { + auto indexAttr = getI64IntegerAttr(i); + auto allocOp = AllocOp::create(*this, loc, nameAttr, sizeAttr, indexAttr); + const auto& qubit = qubits.emplace_back(allocOp.getResult()); + // Track the allocated qubit for automatic deallocation + allocatedQubits.insert(qubit); + } + + return qubits; +} + +QCProgramBuilder::ClassicalRegister +QCProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) const { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + return {.name = std::move(name), .size = size}; +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset +//===----------------------------------------------------------------------===// + +Value QCProgramBuilder::measure(Value qubit) { + checkFinalized(); + auto measureOp = MeasureOp::create(*this, loc, qubit); + return measureOp.getResult(); +} + +QCProgramBuilder& QCProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); + auto nameAttr = getStringAttr(bit.registerName); + auto sizeAttr = getI64IntegerAttr(bit.registerSize); + auto indexAttr = getI64IntegerAttr(bit.registerIndex); + MeasureOp::create(*this, loc, qubit, nameAttr, sizeAttr, indexAttr); + return *this; +} + +QCProgramBuilder& QCProgramBuilder::reset(Value qubit) { + checkFinalized(); + ResetOp::create(*this, loc, qubit); + return *this; +} + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM)) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, PARAM); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM, {control}); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, \ + [&](OpBuilder& b) { OP_CLASS::create(b, loc, PARAM); }); \ + return *this; \ + } + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME(Value qubit) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ + checkFinalized(); \ + return mc##OP_NAME({control}, target); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME(ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, \ + [&](OpBuilder& b) { OP_CLASS::create(b, loc, target); }); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM), Value qubit) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit, PARAM); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control, \ + Value target) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM, {control}, target); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM); \ + }); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM1, PARAM2, {control}, target); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM1, PARAM2); \ + }); \ + return *this; \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value qubit) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM1, PARAM2, PARAM3, {control}, target); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM1, PARAM2, PARAM3); \ + }); \ + return *this; \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME(Value qubit0, Value qubit1) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit0, qubit1); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME(Value control, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + return mc##OP_NAME({control}, qubit0, qubit1); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + ValueRange controls, Value qubit0, Value qubit1) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1); \ + }); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM, {control}, qubit0, qubit1); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1, PARAM); \ + }); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM1, PARAM2); \ + return *this; \ + } \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + return mc##OP_NAME(PARAM1, PARAM2, {control}, qubit0, qubit1); \ + } \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + checkFinalized(); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1, PARAM1, PARAM2); \ + }); \ + return *this; \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +QCProgramBuilder& QCProgramBuilder::barrier(ValueRange qubits) { + checkFinalized(); + BarrierOp::create(*this, loc, qubits); + return *this; +} + +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +QCProgramBuilder& +QCProgramBuilder::ctrl(ValueRange controls, + const std::function& body) { + checkFinalized(); + CtrlOp::create(*this, loc, controls, body); + return *this; +} + +//===----------------------------------------------------------------------===// +// Deallocation +//===----------------------------------------------------------------------===// + +QCProgramBuilder& QCProgramBuilder::dealloc(Value qubit) { + checkFinalized(); + + // Check if the qubit is in the tracking set + if (!allocatedQubits.erase(qubit)) { + // Qubit was not found in the set - either never allocated or already + // deallocated + llvm::reportFatalUsageError( + "Double deallocation or invalid qubit deallocation"); + } + + // Create the DeallocOp + DeallocOp::create(*this, loc, qubit); + + return *this; +} + +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + +void QCProgramBuilder::checkFinalized() const { + if (ctx == nullptr) { + llvm::reportFatalUsageError("QCProgramBuilder instance has been finalized"); + } +} + +OwningOpRef QCProgramBuilder::finalize() { + checkFinalized(); + + // Ensure that main function exists and insertion point is valid + auto* insertionBlock = getInsertionBlock(); + func::FuncOp mainFunc = nullptr; + for (auto op : module.getOps()) { + if (op.getName() == "main") { + mainFunc = op; + break; + } + } + if (!mainFunc) { + llvm::reportFatalUsageError("Could not find main function"); + } + if ((insertionBlock == nullptr) || + insertionBlock != &mainFunc.getBody().front()) { + llvm::reportFatalUsageError( + "Insertion point is not in entry block of main function"); + } + + // Automatically deallocate all still-allocated qubits + // Sort qubits for deterministic output + llvm::SmallVector sortedQubits(allocatedQubits.begin(), + allocatedQubits.end()); + llvm::sort(sortedQubits, [](Value a, Value b) { + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); + }); + for (auto qubit : sortedQubits) { + DeallocOp::create(*this, loc, qubit); + } + + // Clear the tracking set + allocatedQubits.clear(); + + // Create constant 0 for successful exit code + auto exitCode = arith::ConstantOp::create(*this, loc, getI64IntegerAttr(0)); + + // Add return statement with exit code 0 to the main function + func::ReturnOp::create(*this, loc, ValueRange{exitCode}); + + // Invalidate context to prevent use-after-finalize + ctx = nullptr; + + // Transfer ownership to the caller + return module; +} + +} // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/CMakeLists.txt b/mlir/lib/Dialect/QC/CMakeLists.txt new file mode 100644 index 0000000000..2af82c84a4 --- /dev/null +++ b/mlir/lib/Dialect/QC/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) +add_subdirectory(Builder) +add_subdirectory(Translation) diff --git a/mlir/lib/Dialect/QC/IR/CMakeLists.txt b/mlir/lib/Dialect/QC/IR/CMakeLists.txt new file mode 100644 index 0000000000..e76c161d66 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") +file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") + +add_mlir_dialect_library( + MLIRQCDialect + QCOps.cpp + ${MODIFIERS} + ${OPERATIONS} + ${QUBIT_MANAGEMENT} + ADDITIONAL_HEADER_DIRS + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QC + DEPENDS + MLIRQCOpsIncGen + MLIRQCInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRIR + MLIRSideEffectInterfaces) + +# collect header files +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QC/IR/*.inc") + +# add public headers using file sets +target_sources( + MLIRQCDialect + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${IR_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${IR_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp new file mode 100644 index 0000000000..8651cbb12e --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; + +namespace { + +/** + * @brief Merge nested control modifiers into a single one. + */ +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + auto bodyUnitary = op.getBodyUnitary(); + auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + if (!bodyCtrlOp) { + return failure(); + } + + llvm::SmallVector newControls(op.getControls()); + for (const auto control : bodyCtrlOp.getControls()) { + newControls.push_back(control); + } + + rewriter.replaceOpWithNewOp(op, newControls, + bodyCtrlOp.getBodyUnitary()); + + return success(); + } +}; + +/** + * @brief Remove control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (op.getNumControls() > 0) { + return failure(); + } + + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(op); + + rewriter.clone(*op.getBodyUnitary().getOperation()); + rewriter.eraseOp(op); + + return success(); + } +}; + +/** + * @brief Inline controlled GPhase operations. + */ +struct CtrlInlineGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumControls() == 0) { + return failure(); + } + + auto gPhaseOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!gPhaseOp) { + return failure(); + } + + SmallVector newControls(op.getControls()); + const auto newTarget = newControls.back(); + newControls.pop_back(); + CtrlOp::create(rewriter, op.getLoc(), newControls, [&](OpBuilder& b) { + POp::create(b, op.getLoc(), newTarget, gPhaseOp.getTheta()); + }); + rewriter.eraseOp(op); + + return success(); + } +}; + +} // namespace + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { return getBodyUnitary().getNumTargets(); } + +size_t CtrlOp::getNumControls() { return getControls().size(); } + +Value CtrlOp::getQubit(const size_t i) { + const auto numControls = getNumControls(); + if (i < numControls) { + return getControls()[i]; + } + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getQubit(i - numControls); + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value CtrlOp::getTarget(const size_t i) { + return getBodyUnitary().getTarget(i); +} + +Value CtrlOp::getControl(const size_t i) { + if (i >= getNumControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } + return getControls()[i]; +} + +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } + +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange controls, UnitaryOpInterface bodyUnitary) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + // Move the unitary op into the block + odsBuilder.setInsertionPointToStart(&block); + odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange controls, + const std::function& bodyBuilder) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(odsBuilder); + odsBuilder.create(odsState.location); +} + +LogicalResult CtrlOp::verify() { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(block.front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(block.back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + + llvm::SmallPtrSet uniqueQubits; + for (const auto& control : getControls()) { + if (!uniqueQubits.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubits.insert(bodyUnitary.getQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + + if (llvm::isa(bodyUnitary.getOperation())) { + return emitOpError("BarrierOp cannot be controlled"); + } + + return success(); +} + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp new file mode 100644 index 0000000000..bf2bfeb207 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include + +using namespace mlir; +using namespace mlir::qc; + +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp new file mode 100644 index 0000000000..d4cd113931 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; + +size_t BarrierOp::getNumQubits() { return getNumTargets(); } + +size_t BarrierOp::getNumTargets() { return getQubits().size(); } + +size_t BarrierOp::getNumControls() { return 0; } + +Value BarrierOp::getQubit(const size_t i) { return getTarget(i); } + +Value BarrierOp::getTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubits()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getControl(const size_t /*i*/) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +size_t BarrierOp::getNumParams() { return 0; } + +Value BarrierOp::getParameter(const size_t /*i*/) { + llvm::reportFatalUsageError("BarrierOp does not have parameters"); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp new file mode 100644 index 0000000000..82ead6b8dc --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void GPhaseOp::build(OpBuilder& builder, OperationState& state, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp new file mode 100644 index 0000000000..8619fe04c6 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp new file mode 100644 index 0000000000..5405e3f764 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta, + const std::variant& phi) { + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + build(builder, state, qubitIn, thetaOperand, phiOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp new file mode 100644 index 0000000000..e8d108caed --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp new file mode 100644 index 0000000000..dad4d0610a --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp new file mode 100644 index 0000000000..973c7fd194 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp new file mode 100644 index 0000000000..b6550b1f6d --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp new file mode 100644 index 0000000000..9f770797e5 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp new file mode 100644 index 0000000000..ba9a821cd0 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp new file mode 100644 index 0000000000..67fe5ce2e7 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp new file mode 100644 index 0000000000..03251e2157 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& phi, + const std::variant& lambda) { + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp new file mode 100644 index 0000000000..456de9175c --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta, + const std::variant& phi, + const std::variant& lambda) { + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp new file mode 100644 index 0000000000..50f72a4cca --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, + Value qubit0In, Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp new file mode 100644 index 0000000000..d93f0b3519 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; + +void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, + Value qubit0In, Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp new file mode 100644 index 0000000000..06903c98c9 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" // IWYU pragma: associated + +// The following headers are needed for some template instantiations. +// IWYU pragma: begin_keep +#include +#include +#include +// IWYU pragma: end_keep + +using namespace mlir; +using namespace mlir::qc; + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QC/IR/QCOpsDialect.cpp.inc" + +void QCDialect::initialize() { + // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" + + >(); + + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/QC/IR/QCOps.cpp.inc" + + >(); +} + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QC/IR/QCInterfaces.cpp.inc" + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/QC/IR/QCOps.cpp.inc" diff --git a/mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp new file mode 100644 index 0000000000..2048f4cc65 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include + +using namespace mlir; +using namespace mlir::qc; + +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp new file mode 100644 index 0000000000..6fea22d66b --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/IR/QCDialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; + +namespace { + +/** + * @brief Remove matching allocation and deallocation pairs without operations + * between them. + */ +struct RemoveAllocDeallocPair final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(DeallocOp op, + PatternRewriter& rewriter) const override { + // Get the AllocOp defining the qubit + auto allocOp = op.getQubit().getDefiningOp(); + if (!allocOp) { + return failure(); + } + + // Check if the qubit has no other uses + if (!op.getQubit().hasOneUse()) { + return failure(); + } + + // Remove the AllocOp and the DeallocOp + rewriter.eraseOp(op); + rewriter.eraseOp(allocOp); + return success(); + } +}; + +} // namespace + +void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt new file mode 100644 index 0000000000..5414559cb6 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_compile_options("-fexceptions") + +add_mlir_library( + MLIRQCTranslation + TranslateQuantumComputationToQC.cpp + LINK_LIBS + MLIRArithDialect + MLIRFuncDialect + MLIRSCFDialect + MLIRQCDialect + MQT::CoreIR) + +# collect header files +file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Translation/*.h) + +# add public headers using file sets +target_sources(MLIRQCTranslation PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${TRANSLATION_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp new file mode 100644 index 0000000000..0a15224437 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" + +#include "ir/QuantumComputation.hpp" +#include "ir/Register.hpp" +#include "ir/operations/Control.hpp" +#include "ir/operations/NonUnitaryOperation.hpp" +#include "ir/operations/OpType.hpp" +#include "ir/operations/Operation.hpp" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { + +using namespace qc; + +namespace { + +/** + * @brief Structure to store information about quantum registers + * + * @details + * Maps quantum registers from the input QuantumComputation to their + * corresponding MLIR qubit values allocated via the QCProgramBuilder. + */ +struct QregInfo { + const ::qc::QuantumRegister* qregPtr; + SmallVector qubits; +}; + +using BitMemInfo = std::pair; // (register ref, localIdx) +using BitIndexVec = SmallVector; + +} // namespace + +/** + * @brief Allocates quantum registers using the QCProgramBuilder + * + * @details + * Processes all quantum and ancilla registers from the QuantumComputation, + * sorting them by start index, and allocates them using the builder's + * allocQubitRegister method which generates qc.alloc operations with + * proper register metadata. + * + * @param builder The QCProgramBuilder used to create operations + * @param quantumComputation The quantum computation to translate + * @return Vector containing information about all quantum registers + */ +static SmallVector +allocateQregs(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation) { + // Build list of pointers for sorting + SmallVector qregPtrs; + qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + + quantumComputation.getAncillaRegisters().size()); + for (const auto& qreg : + quantumComputation.getQuantumRegisters() | std::views::values) { + qregPtrs.emplace_back(&qreg); + } + for (const auto& qreg : + quantumComputation.getAncillaRegisters() | std::views::values) { + qregPtrs.emplace_back(&qreg); + } + + // Sort by start index + std::ranges::sort(qregPtrs, [](const ::qc::QuantumRegister* a, + const ::qc::QuantumRegister* b) { + return a->getStartIndex() < b->getStartIndex(); + }); + + // Allocate quantum registers using the builder + SmallVector qregs; + for (const auto* qregPtr : qregPtrs) { + auto qubits = builder.allocQubitRegister( + static_cast(qregPtr->getSize()), qregPtr->getName()); + qregs.emplace_back(qregPtr, std::move(qubits)); + } + + return qregs; +} + +/** + * @brief Builds a flat mapping from global qubit index to qubit value + * + * @details + * Creates a flat array where each index corresponds to a physical qubit + * index, and the value is the MLIR Value representing that qubit. This + * simplifies operation translation by providing direct qubit lookup. + * + * @param quantumComputation The quantum computation being translated + * @param qregs Vector containing information about all quantum registers + * @return Flat vector of qubit values indexed by physical qubit index + */ +static SmallVector +buildQubitMap(const ::qc::QuantumComputation& quantumComputation, + const SmallVector& qregs) { + SmallVector flatQubits; + const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); + flatQubits.resize(static_cast(maxPhys) + 1); + + for (const auto& qreg : qregs) { + for (size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { + const auto globalIdx = qreg.qregPtr->getStartIndex() + i; + flatQubits[globalIdx] = qreg.qubits[i]; + } + } + + return flatQubits; +} + +/** + * @brief Allocates classical registers using the QCProgramBuilder + * + * @details + * Creates classical bit registers and builds a mapping from global classical + * bit indices to (register, local_index) pairs. This is used for measurement + * result storage. + * + * @param builder The QCProgramBuilder used to create operations + * @param quantumComputation The quantum computation to translate + * @return Vector mapping global bit indices to register and local indices + */ +static BitIndexVec +allocateClassicalRegisters(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation) { + // Build list of pointers for sorting + SmallVector cregPtrs; + cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); + for (const auto& reg : + quantumComputation.getClassicalRegisters() | std::views::values) { + cregPtrs.emplace_back(®); + } + + // Sort by start index + std::ranges::sort(cregPtrs, [](const ::qc::ClassicalRegister* a, + const ::qc::ClassicalRegister* b) { + return a->getStartIndex() < b->getStartIndex(); + }); + + // Build mapping using the builder + BitIndexVec bitMap; + bitMap.resize(quantumComputation.getNcbits()); + for (const auto* reg : cregPtrs) { + const auto& mem = builder.allocClassicalBitRegister( + static_cast(reg->getSize()), reg->getName()); + for (size_t i = 0; i < reg->getSize(); ++i) { + const auto globalIdx = static_cast(reg->getStartIndex() + i); + bitMap[globalIdx] = {mem, i}; + } + } + + return bitMap; +} + +/** + * @brief Adds measurement operations for a single operation + * + * @details + * Translates measurement operations from the QuantumComputation to + * qc.measure operations, storing results in classical registers. + * + * @param builder The QCProgramBuilder used to create operations + * @param operation The measurement operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param bitMap Mapping from global bit index to (register, local_index) + */ +static void addMeasureOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits, + const BitIndexVec& bitMap) { + const auto& measureOp = + dynamic_cast(operation); + const auto& targets = measureOp.getTargets(); + const auto& classics = measureOp.getClassics(); + + for (size_t i = 0; i < targets.size(); ++i) { + const auto& qubit = qubits[targets[i]]; + const auto bitIdx = static_cast(classics[i]); + const auto& [mem, localIdx] = bitMap[bitIdx]; + + // Use builder's measure method which keeps output record + builder.measure(qubit, mem[static_cast(localIdx)]); + } +} + +/** + * @brief Adds reset operations for a single operation + * + * @details + * Translates reset operations from the QuantumComputation to + * qc.reset operations. + * + * @param builder The QCProgramBuilder used to create operations + * @param operation The reset operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +static void addResetOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits) { + for (const auto& target : operation.getTargets()) { + auto qubit = qubits[target]; + builder.reset(qubit); + } +} + +/** + * @brief Extracts positive control qubits from an operation + * + * @details + * Iterates through the controls of the given operation and collects + * the qubit values corresponding to positive controls. + * + * @param operation The operation containing controls + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @return Vector of qubit values corresponding to positive controls + */ +static SmallVector getControls(const ::qc::Operation& operation, + const SmallVector& qubits) { + SmallVector controls; + for (const auto& [control, type] : operation.getControls()) { + if (type == ::qc::Control::Type::Neg) { + llvm::reportFatalInternalError( + "Negative controls cannot be translated to QC at the moment"); + } + controls.push_back(qubits[control]); + } + return controls; +} + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(target); \ + } else { \ + builder.mc##OP_QC(controls, target); \ + } \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(I, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(X, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(H, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(S, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Sdg, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(T, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Tdg, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SX, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& param = operation.getParameter()[0]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(param, target); \ + } else { \ + builder.mc##OP_QC(param, controls, target); \ + } \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RX, rx) +DEFINE_ONE_TARGET_ONE_PARAMETER(RY, ry) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZ, rz) +DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(param1, param2, target); \ + } else { \ + builder.mc##OP_QC(param1, param2, controls, target); \ + } \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(R, r) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& param3 = operation.getParameter()[2]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(param1, param2, param3, target); \ + } else { \ + builder.mc##OP_QC(param1, param2, param3, controls, target); \ + } \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(target0, target1); \ + } else { \ + builder.mc##OP_QC(controls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAP, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAP, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCX, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& param = operation.getParameter()[0]; \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(param, target0, target1); \ + } else { \ + builder.mc##OP_QC(param, controls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYY, ryy) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZX, rzx) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CORE, OP_QC) \ + /** \ + * @brief Adds a qc.OP_QC operation \ + * \ + * @details \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ + * \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ + builder.OP_QC(param1, param2, target0, target1); \ + } else { \ + builder.mc##OP_QC(param1, param2, controls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXplusYY, xx_plus_yy) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +static void addBarrierOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits) { + SmallVector targets; + for (const auto& targetIdx : operation.getTargets()) { + targets.push_back(qubits[targetIdx]); + } + builder.barrier(targets); +} + +#define ADD_OP_CASE(OP_CORE) \ + case ::qc::OpType::OP_CORE: \ + add##OP_CORE##Op(builder, *operation, qubits); \ + break; + +/** + * @brief Translates operations from QuantumComputation to QC dialect + * + * @details + * Iterates through all operations in the QuantumComputation and translates + * them to QC operations. + * + * @param builder The QCProgramBuilder used to create operations + * @param quantumComputation The quantum computation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param bitMap Mapping from global bit index to (register, local_index) + * @return Success if all supported operations were translated + */ +static LogicalResult +translateOperations(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation, + const SmallVector& qubits, + const BitIndexVec& bitMap) { + if (quantumComputation.hasGlobalPhase()) { + builder.gphase(quantumComputation.getGlobalPhase()); + } + for (const auto& operation : quantumComputation) { + switch (operation->getType()) { + case ::qc::OpType::Measure: + addMeasureOp(builder, *operation, qubits, bitMap); + break; + ADD_OP_CASE(Reset) + ADD_OP_CASE(I) + ADD_OP_CASE(X) + ADD_OP_CASE(Y) + ADD_OP_CASE(Z) + ADD_OP_CASE(H) + ADD_OP_CASE(S) + ADD_OP_CASE(Sdg) + ADD_OP_CASE(T) + ADD_OP_CASE(Tdg) + ADD_OP_CASE(SX) + ADD_OP_CASE(SXdg) + ADD_OP_CASE(RX) + ADD_OP_CASE(RY) + ADD_OP_CASE(RZ) + ADD_OP_CASE(P) + ADD_OP_CASE(R) + ADD_OP_CASE(U2) + ADD_OP_CASE(U) + ADD_OP_CASE(SWAP) + ADD_OP_CASE(iSWAP) + ADD_OP_CASE(DCX) + ADD_OP_CASE(ECR) + ADD_OP_CASE(RXX) + ADD_OP_CASE(RYY) + ADD_OP_CASE(RZX) + ADD_OP_CASE(RZZ) + ADD_OP_CASE(XXplusYY) + ADD_OP_CASE(XXminusYY) + ADD_OP_CASE(Barrier) + default: + llvm::errs() << operation->getName() << " cannot be translated to QC\n"; + return failure(); + } + } + + return success(); +} + +#undef ADD_OP_CASE + +/** + * @brief Translates a QuantumComputation to an MLIR module with QC + * operations + * + * @details + * This function takes a quantum computation and translates it into an MLIR + * module containing QC operations. It uses the QCProgramBuilder to + * handle module and function creation, resource allocation, and operation + * translation. + * + * The translation process: + * 1. Creates a QCProgramBuilder and initializes it (creates the main + * function) + * 2. Allocates quantum registers using qc.alloc + * 3. Tracks classical registers for measurement results + * 4. Translates operations + * 5. Finalizes the module (adds return statement with exit code 0) + * + * If the translation fails due to an unsupported operation, a fatal error is + * reported. + * + * @param context The MLIR context in which the module will be created + * @param quantumComputation The quantum computation to translate + * @return OwningOpRef containing the translated MLIR module + */ +OwningOpRef translateQuantumComputationToQC( + MLIRContext* context, const ::qc::QuantumComputation& quantumComputation) { + // Create and initialize the builder (creates module and main function) + QCProgramBuilder builder(context); + builder.initialize(); + + // Allocate quantum registers using the builder + const auto qregs = allocateQregs(builder, quantumComputation); + + // Build flat qubit mapping for easy lookup + const auto qubits = buildQubitMap(quantumComputation, qregs); + + // Allocate classical registers using the builder + const auto bitMap = allocateClassicalRegisters(builder, quantumComputation); + + // Translate operations + if (translateOperations(builder, quantumComputation, qubits, bitMap) + .failed()) { + llvm::reportFatalInternalError( + "Failed to translate QuantumComputation to QC"); + } + + // Finalize and return the module (adds return statement and transfers + // ownership) + return builder.finalize(); +} + +} // namespace mlir diff --git a/mlir/lib/Dialect/QCO/Builder/CMakeLists.txt b/mlir/lib/Dialect/QCO/Builder/CMakeLists.txt new file mode 100644 index 0000000000..6446970949 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Builder/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRQCOProgramBuilder + QCOProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRArithDialect + MLIRFuncDialect + MLIRSCFDialect + MLIRQCODialect) + +# collect header files +file(GLOB_RECURSE BUILDER_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRQCOProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp new file mode 100644 index 0000000000..a3fa2123b7 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { + +QCOProgramBuilder::QCOProgramBuilder(MLIRContext* context) + : OpBuilder(context), ctx(context), loc(getUnknownLoc()), + module(ModuleOp::create(loc)) { + ctx->loadDialect(); +} + +void QCOProgramBuilder::initialize() { + // Set insertion point to the module body + setInsertionPointToStart(module.getBody()); + + // Create main function as entry point + auto funcType = getFunctionType({}, {getI64Type()}); + auto mainFunc = func::FuncOp::create(*this, loc, "main", funcType); + + // Add entry_point attribute to identify the main function + auto entryPointAttr = getStringAttr("entry_point"); + mainFunc->setAttr("passthrough", getArrayAttr({entryPointAttr})); + + // Create entry block and set insertion point + auto& entryBlock = mainFunc.getBody().emplaceBlock(); + setInsertionPointToStart(&entryBlock); +} + +Value QCOProgramBuilder::allocQubit() { + checkFinalized(); + + auto allocOp = AllocOp::create(*this, loc); + const auto qubit = allocOp.getResult(); + + // Track the allocated qubit as valid + validQubits.insert(qubit); + + return qubit; +} + +Value QCOProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + + auto indexAttr = getI64IntegerAttr(index); + auto staticOp = StaticOp::create(*this, loc, indexAttr); + const auto qubit = staticOp.getQubit(); + + // Track the static qubit as valid + validQubits.insert(qubit); + + return qubit; +} + +llvm::SmallVector +QCOProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + llvm::SmallVector qubits; + qubits.reserve(static_cast(size)); + + auto nameAttr = getStringAttr(name); + auto sizeAttr = getI64IntegerAttr(size); + + for (int64_t i = 0; i < size; ++i) { + const auto indexAttr = getI64IntegerAttr(i); + auto allocOp = AllocOp::create(*this, loc, nameAttr, sizeAttr, indexAttr); + const auto& qubit = qubits.emplace_back(allocOp.getResult()); + // Track the allocated qubit as valid + validQubits.insert(qubit); + } + + return qubits; +} + +QCOProgramBuilder::ClassicalRegister +QCOProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) const { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + return {.name = std::move(name), .size = size}; +} + +//===----------------------------------------------------------------------===// +// Linear Type Tracking Helpers +//===----------------------------------------------------------------------===// + +void QCOProgramBuilder::validateQubitValue(Value qubit) const { + if (!validQubits.contains(qubit)) { + llvm::errs() << "Attempting to use an invalid qubit SSA value. " + << "The value may have been consumed by a previous operation " + << "or was never created through this builder.\n"; + llvm::reportFatalUsageError( + "Invalid qubit value used (either consumed or not tracked)"); + } +} + +void QCOProgramBuilder::updateQubitTracking(Value inputQubit, + Value outputQubit) { + // Validate the input qubit + validateQubitValue(inputQubit); + + // Remove the input (consumed) value from tracking + validQubits.erase(inputQubit); + + // Add the output (new) value to tracking + validQubits.insert(outputQubit); +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset +//===----------------------------------------------------------------------===// + +std::pair QCOProgramBuilder::measure(Value qubit) { + checkFinalized(); + + auto measureOp = MeasureOp::create(*this, loc, qubit); + auto qubitOut = measureOp.getQubitOut(); + auto result = measureOp.getResult(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return {qubitOut, result}; +} + +Value QCOProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); + + auto nameAttr = getStringAttr(bit.registerName); + auto sizeAttr = getI64IntegerAttr(bit.registerSize); + auto indexAttr = getI64IntegerAttr(bit.registerIndex); + auto measureOp = + MeasureOp::create(*this, loc, qubit, nameAttr, sizeAttr, indexAttr); + const auto qubitOut = measureOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + +Value QCOProgramBuilder::reset(Value qubit) { + checkFinalized(); + + auto resetOp = ResetOp::create(*this, loc, qubit); + const auto qubitOut = resetOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + void QCOProgramBuilder::OP_NAME(const std::variant&(PARAM)) { \ + checkFinalized(); \ + OP_CLASS::create(*this, loc, PARAM); \ + } \ + Value QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control) { \ + checkFinalized(); \ + const auto controlsOut = \ + ctrl(control, {}, \ + [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ + OP_CLASS::create(b, loc, PARAM); \ + return {}; \ + }) \ + .first; \ + return controlsOut[0]; \ + } \ + ValueRange QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls) { \ + checkFinalized(); \ + const auto controlsOut = \ + ctrl(controls, {}, \ + [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ + OP_CLASS::create(b, loc, PARAM); \ + return {}; \ + }) \ + .first; \ + return controlsOut; \ + } + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + Value QCOProgramBuilder::OP_NAME(Value qubit) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ + } \ + std::pair QCOProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0]); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ + } \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ + ValueRange controls, Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0]); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM), \ + Value qubit) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ + } \ + std::pair QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ + } \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ + } \ + std::pair QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ + } \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ + } \ + std::pair QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2, PARAM3); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ + } \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM1, \ + PARAM2, PARAM3); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + std::pair QCOProgramBuilder::OP_NAME(Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ + } \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ + Value control, Value qubit0, Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], targets[1]); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ + } \ + std::pair> \ + QCOProgramBuilder::mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], targets[1]); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + std::pair QCOProgramBuilder::OP_NAME( \ + const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ + } \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], targets[1], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ + } \ + std::pair> \ + QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], targets[1], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + std::pair QCOProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM1, PARAM2); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ + } \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0], \ + targets[1], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ + } \ + std::pair> \ + QCOProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + checkFinalized(); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = OP_CLASS::create(b, loc, targets[0], \ + targets[1], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +// BarrierOp + +ValueRange QCOProgramBuilder::barrier(ValueRange qubits) { + checkFinalized(); + + auto op = BarrierOp::create(*this, loc, qubits); + const auto& qubitsOut = op.getQubitsOut(); + for (const auto& [inputQubit, outputQubit] : llvm::zip(qubits, qubitsOut)) { + updateQubitTracking(inputQubit, outputQubit); + } + return qubitsOut; +} + +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +std::pair QCOProgramBuilder::ctrl( + ValueRange controls, ValueRange targets, + const std::function& body) { + checkFinalized(); + + auto ctrlOp = CtrlOp::create(*this, loc, controls, targets, body); + + // Update tracking + const auto& controlsOut = ctrlOp.getControlsOut(); + for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + updateQubitTracking(control, controlOut); + } + const auto& targetsOut = ctrlOp.getTargetsOut(); + for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { + updateQubitTracking(target, targetOut); + } + + return {controlsOut, targetsOut}; +} + +//===----------------------------------------------------------------------===// +// Deallocation +//===----------------------------------------------------------------------===// + +QCOProgramBuilder& QCOProgramBuilder::dealloc(Value qubit) { + checkFinalized(); + + validateQubitValue(qubit); + validQubits.erase(qubit); + + DeallocOp::create(*this, loc, qubit); + + return *this; +} + +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + +void QCOProgramBuilder::checkFinalized() const { + if (ctx == nullptr) { + llvm::reportFatalUsageError( + "QCOProgramBuilder instance has been finalized"); + } +} + +OwningOpRef QCOProgramBuilder::finalize() { + checkFinalized(); + + // Ensure that main function exists and insertion point is valid + auto* insertionBlock = getInsertionBlock(); + func::FuncOp mainFunc = nullptr; + for (auto op : module.getOps()) { + if (op.getName() == "main") { + mainFunc = op; + break; + } + } + if (!mainFunc) { + llvm::reportFatalUsageError("Could not find main function"); + } + if ((insertionBlock == nullptr) || + insertionBlock != &mainFunc.getBody().front()) { + llvm::reportFatalUsageError( + "Insertion point is not in entry block of main function"); + } + + // Automatically deallocate all still-allocated qubits + // Sort qubits for deterministic output + llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); + llvm::sort(sortedQubits, [](Value a, Value b) { + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); + }); + for (auto qubit : sortedQubits) { + DeallocOp::create(*this, loc, qubit); + } + + validQubits.clear(); + + // Create constant 0 for successful exit code + auto exitCode = arith::ConstantOp::create(*this, loc, getI64IntegerAttr(0)); + + // Add return statement with exit code 0 to the main function + func::ReturnOp::create(*this, loc, ValueRange{exitCode}); + + // Invalidate context to prevent use-after-finalize + ctx = nullptr; + + return module; +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/CMakeLists.txt b/mlir/lib/Dialect/QCO/CMakeLists.txt new file mode 100644 index 0000000000..77b183713f --- /dev/null +++ b/mlir/lib/Dialect/QCO/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(Builder) +add_subdirectory(IR) diff --git a/mlir/lib/Dialect/QCO/IR/CMakeLists.txt b/mlir/lib/Dialect/QCO/IR/CMakeLists.txt new file mode 100644 index 0000000000..567a80611b --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") +file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") + +add_mlir_dialect_library( + MLIRQCODialect + QCOOps.cpp + ${MODIFIERS} + ${OPERATIONS} + ${QUBIT_MANAGEMENT} + ADDITIONAL_HEADER_DIRS + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QCO + DEPENDS + MLIRQCOOpsIncGen + MLIRQCOInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRIR + MLIRSideEffectInterfaces) + +# collect header files +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/IR/*.inc") + +# add public headers using file sets +target_sources( + MLIRQCODialect + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${IR_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${IR_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp new file mode 100644 index 0000000000..44a40bd64c --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Merge nested control modifiers into a single one. + */ +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumControls() == 0) { + return failure(); + } + + auto bodyCtrlOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!bodyCtrlOp) { + return failure(); + } + + // Merge controls + SmallVector newControls(op.getControlsIn()); + for (const auto control : bodyCtrlOp.getControlsIn()) { + newControls.push_back(control); + } + + rewriter.replaceOpWithNewOp(op, newControls, op.getTargetsIn(), + bodyCtrlOp.getBodyUnitary()); + + return success(); + } +}; + +/** + * @brief Remove control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (op.getNumControls() > 0) { + return failure(); + } + + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(op); + + auto* clonedBody = rewriter.clone(*op.getBodyUnitary().getOperation()); + rewriter.replaceOp(op, clonedBody->getResults()); + + return success(); + } +}; + +/** + * @brief Inline controlled GPhase operations. + */ +struct CtrlInlineGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumControls() == 0) { + return failure(); + } + + auto gPhaseOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!gPhaseOp) { + return failure(); + } + + SmallVector newControls(op.getControlsIn()); + const auto newTarget = newControls.back(); + newControls.pop_back(); + auto ctrlOp = CtrlOp::create(rewriter, op.getLoc(), newControls, newTarget, + [&](OpBuilder& b, ValueRange targets) { + auto pOp = + POp::create(b, op.getLoc(), targets[0], + gPhaseOp.getTheta()); + return pOp.getQubitOut(); + }); + + rewriter.replaceOp(op, ctrlOp.getResults()); + + return success(); + } +}; + +/** + * @brief Inline controlled identity operations. + */ +struct CtrlInlineId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumControls() == 0) { + return failure(); + } + + if (!llvm::isa(op.getBodyUnitary().getOperation())) { + return failure(); + } + + auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); + + SmallVector newOperands; + newOperands.reserve(op.getNumControls() + 1); + newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); + newOperands.push_back(idOp.getQubitOut()); + rewriter.replaceOp(op, newOperands); + + return success(); + } +}; + +} // namespace + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { return getTargetsIn().size(); } + +size_t CtrlOp::getNumControls() { return getControlsIn().size(); } + +Value CtrlOp::getInputQubit(const size_t i) { + const auto numControls = getNumControls(); + if (i < numControls) { + return getControlsIn()[i]; + } + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getInputQubit(i - numControls); + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value CtrlOp::getOutputQubit(const size_t i) { + const auto numControls = getNumControls(); + if (i < numControls) { + return getControlsOut()[i]; + } + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getOutputQubit(i - numControls); + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value CtrlOp::getInputTarget(const size_t i) { + if (i >= getNumTargets()) { + llvm::reportFatalUsageError("Target index out of bounds"); + } + return getTargetsIn()[i]; +} + +Value CtrlOp::getOutputTarget(const size_t i) { + if (i >= getNumTargets()) { + llvm::reportFatalUsageError("Target index out of bounds"); + } + return getTargetsOut()[i]; +} + +Value CtrlOp::getInputControl(const size_t i) { + if (i >= getNumControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } + return getControlsIn()[i]; +} + +Value CtrlOp::getOutputControl(const size_t i) { + if (i >= getNumControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } + return getControlsOut()[i]; +} + +Value CtrlOp::getInputForOutput(Value output) { + for (size_t i = 0; i < getNumControls(); ++i) { + if (output == getControlsOut()[i]) { + return getControlsIn()[i]; + } + } + for (size_t i = 0; i < getNumTargets(); ++i) { + if (output == getTargetsOut()[i]) { + return getTargetsIn()[i]; + } + } + llvm::reportFatalUsageError("Given qubit is not an output of the operation"); +} + +Value CtrlOp::getOutputForInput(Value input) { + for (size_t i = 0; i < getNumControls(); ++i) { + if (input == getControlsIn()[i]) { + return getControlsOut()[i]; + } + } + for (size_t i = 0; i < getNumTargets(); ++i) { + if (input == getTargetsIn()[i]) { + return getTargetsOut()[i]; + } + } + llvm::reportFatalUsageError("Given qubit is not an input of the operation"); +} + +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } + +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange controls, ValueRange targets, + UnitaryOpInterface bodyUnitary) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location, op->getResults()); +} + +void CtrlOp::build( + OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, + ValueRange targets, + const std::function& bodyBuilder) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(odsBuilder, targets); + odsBuilder.create(odsState.location, targetsOut); +} + +LogicalResult CtrlOp::verify() { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(block.front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(block.back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + if (block.back().getNumOperands() != getNumTargets()) { + return emitOpError("yield operation must yield ") + << getNumTargets() << " values, but found " + << block.back().getNumOperands(); + } + + SmallPtrSet uniqueQubitsIn; + for (const auto& control : getControlsIn()) { + if (!uniqueQubitsIn.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsIn.insert(bodyUnitary.getInputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + SmallPtrSet uniqueQubitsOut; + for (const auto& control : getControlsOut()) { + if (!uniqueQubitsOut.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsOut.insert(bodyUnitary.getOutputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + + if (llvm::isa(bodyUnitary.getOperation())) { + return emitOpError("BarrierOp cannot be controlled"); + } + + return success(); +} + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results + .add( + context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp new file mode 100644 index 0000000000..7910f6c043 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include + +using namespace mlir; +using namespace mlir::qco; + +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp new file mode 100644 index 0000000000..861b6ca331 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove reset operations that immediately follow an allocation. + */ +struct RemoveResetAfterAlloc final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ResetOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + if (auto allocOp = op.getQubitIn().getDefiningOp(); !allocOp) { + return failure(); + } + + // Remove the ResetOp + rewriter.replaceOp(op, op.getQubitIn()); + return success(); + } +}; + +} // namespace + +void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp new file mode 100644 index 0000000000..c108f87cf3 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Merge subsequent barriers on the same qubits into a single barrier. + */ +struct MergeSubsequentBarrier final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(BarrierOp op, + PatternRewriter& rewriter) const override { + const auto& qubitsIn = op.getQubitsIn(); + + auto anythingToMerge = false; + DenseMap newQubitsOutMap; + + SmallVector newQubitsIn; + SmallVector indicesToFill; + + for (size_t i = 0; i < qubitsIn.size(); ++i) { + if (llvm::isa( + *op.getOutputForInput(qubitsIn[i]).getUsers().begin())) { + anythingToMerge = true; + newQubitsOutMap[i] = qubitsIn[i]; + } else { + newQubitsIn.push_back(qubitsIn[i]); + indicesToFill.push_back(i); + } + } + + if (!anythingToMerge) { + return failure(); + } + + auto newBarrier = rewriter.create(op.getLoc(), newQubitsIn); + + for (size_t i = 0; i < indicesToFill.size(); ++i) { + newQubitsOutMap[indicesToFill[i]] = newBarrier.getQubitsOut()[i]; + } + + SmallVector newQubitsOut; + newQubitsOut.reserve(op.getQubitsIn().size()); + for (size_t i = 0; i < op.getQubitsIn().size(); ++i) { + newQubitsOut.push_back(newQubitsOutMap[i]); + } + + rewriter.replaceOp(op, newQubitsOut); + return success(); + } +}; + +} // namespace + +size_t BarrierOp::getNumQubits() { return getNumTargets(); } + +size_t BarrierOp::getNumTargets() { return getQubitsIn().size(); } + +size_t BarrierOp::getNumControls() { return 0; } + +Value BarrierOp::getInputQubit(const size_t i) { return getInputTarget(i); } + +Value BarrierOp::getOutputQubit(const size_t i) { return getOutputTarget(i); } + +Value BarrierOp::getInputTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubitsIn()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getOutputTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubitsOut()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getInputControl(const size_t /*i*/) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getOutputControl(const size_t /*i*/) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getInputForOutput(Value output) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (output == getQubitsOut()[i]) { + return getQubitsIn()[i]; + } + } + llvm::reportFatalUsageError("Given qubit is not an output of the operation"); +} + +Value BarrierOp::getOutputForInput(Value input) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (input == getQubitsIn()[i]) { + return getQubitsOut()[i]; + } + } + llvm::reportFatalUsageError("Given qubit is not an input of the operation"); +} + +size_t BarrierOp::getNumParams() { return 0; } + +Value BarrierOp::getParameter(const size_t /*i*/) { + llvm::reportFatalUsageError("BarrierOp has no parameters"); +} + +void BarrierOp::build(OpBuilder& builder, OperationState& state, + ValueRange qubits) { + SmallVector resultTypes; + resultTypes.reserve(qubits.size()); + for (auto qubit : qubits) { + resultTypes.push_back(qubit.getType()); + } + build(builder, state, resultTypes, qubits); +} + +void BarrierOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp new file mode 100644 index 0000000000..fb733e5fdb --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent ECR operations on the same qubits. + */ +struct RemoveSubsequentECR final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ECROp op, + PatternRewriter& rewriter) const override { + return removeInversePairTwoTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void ECROp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp new file mode 100644 index 0000000000..2078d40ce5 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove trivial GPhase operations. + */ +struct RemoveTrivialGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(GPhaseOp op, + PatternRewriter& rewriter) const override { + const auto thetaAttr = GPhaseOp::getStaticParameter(op.getTheta()); + if (!thetaAttr) { + return failure(); + } + + const auto thetaValue = thetaAttr.getValueAsDouble(); + if (std::abs(thetaValue) > TOLERANCE) { + return failure(); + } + + rewriter.eraseOp(op); + return success(); + } +}; + +} // namespace + +void GPhaseOp::build(OpBuilder& builder, OperationState& state, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, thetaOperand); +} + +void GPhaseOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp new file mode 100644 index 0000000000..3b8aa08cd6 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent H operations on the same qubit. + */ +struct RemoveSubsequentH final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(HOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void HOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp new file mode 100644 index 0000000000..7bd721efb8 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove identity operations. + */ +struct RemoveId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(IdOp op, + PatternRewriter& rewriter) const override { + rewriter.replaceOp(op, op.getQubitIn()); + return success(); + } +}; + +} // namespace + +void IdOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp new file mode 100644 index 0000000000..776092ce99 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent P operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(POp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial P operations. + */ +struct RemoveTrivialP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(POp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} + +void POp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp new file mode 100644 index 0000000000..eee14013b6 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Replace R(theta, 0) with RX(theta). + */ +struct ReplaceRWithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ROp op, + PatternRewriter& rewriter) const override { + const auto phi = ROp::getStaticParameter(op.getPhi()); + if (!phi) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + if (std::abs(phiValue) > TOLERANCE) { + return failure(); + } + + auto rxOp = + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace R(theta, pi / 2) with RY(theta). + */ +struct ReplaceRWithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ROp op, + PatternRewriter& rewriter) const override { + const auto phi = ROp::getStaticParameter(op.getPhi()); + if (!phi) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + if (std::abs(phiValue - (std::numbers::pi / 2.0)) > TOLERANCE) { + return failure(); + } + + auto ryOp = + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + +} // namespace + +void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta, + const std::variant& phi) { + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + build(builder, state, qubitIn, thetaOperand, phiOperand); +} + +void ROp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp new file mode 100644 index 0000000000..3be4877abd --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RX operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RX operations. + */ +struct RemoveTrivialRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} + +void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp new file mode 100644 index 0000000000..b3232e11d8 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RXX operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRXX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXXOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RXX operations. + */ +struct RemoveTrivialRXX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} + +void RXXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp new file mode 100644 index 0000000000..8cc3047c2e --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RY operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RY operations. + */ +struct RemoveTrivialRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} + +void RYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp new file mode 100644 index 0000000000..bf8ad965b7 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RYY operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYYOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RYY operations. + */ +struct RemoveTrivialRYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYYOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} + +void RYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp new file mode 100644 index 0000000000..92bfe37b71 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RZ operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RZ operations. + */ +struct RemoveTrivialRZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); +} + +void RZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp new file mode 100644 index 0000000000..c1fcdaaa51 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RZX operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRZX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZXOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RZX operations. + */ +struct RemoveTrivialRZX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} + +void RZXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp new file mode 100644 index 0000000000..df5e35f5fd --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RZZ operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRZZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZZOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +/** + * @brief Remove trivial RZZ operations. + */ +struct RemoveTrivialRZZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZZOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + +void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { + auto thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); +} + +void RZZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp new file mode 100644 index 0000000000..85a98cf235 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove S operations that immediately follow Sdg operations. + */ +struct RemoveSAfterSdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent S operations on the same qubit into a Z operation. + */ +struct MergeSubsequentS final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void SOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp new file mode 100644 index 0000000000..8946bd107f --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent SWAP operations on the same qubits. + */ +struct RemoveSubsequentSWAP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SWAPOp op, + PatternRewriter& rewriter) const override { + return removeInversePairTwoTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void SWAPOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp new file mode 100644 index 0000000000..898c5a77d8 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove SX operations that immediately follow SXdg operations. + */ +struct RemoveSXAfterSXdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent SX operations on the same qubit into an X operation. + */ +struct MergeSubsequentSX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp new file mode 100644 index 0000000000..f82bc7889f --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove SXdg operations that immediately follow SX operations. + */ +struct RemoveSXdgAfterSX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent SXdg operations on the same qubit into an X + * operation. + */ +struct MergeSubsequentSXdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp new file mode 100644 index 0000000000..01e64a1399 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove Sdg operations that immediately follow S operations. + */ +struct RemoveSdgAfterS final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent Sdg operations on the same qubit into a Z operation. + */ +struct MergeSubsequentSdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp new file mode 100644 index 0000000000..6f692a0d89 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove T operations that immediately follow Tdg operations. + */ +struct RemoveTAfterTdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent T operations on the same qubit into an S operation. + */ +struct MergeSubsequentT final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void TOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp new file mode 100644 index 0000000000..301d8e1477 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove Tdg operations that immediately follow T operations. + */ +struct RemoveTdgAfterT final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +/** + * @brief Merge subsequent Tdg operations on the same qubit into an Sdg + * operation. + */ +struct MergeSubsequentTdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp new file mode 100644 index 0000000000..f576f2ba57 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Replace U2(0, pi) with H. + */ +struct ReplaceU2WithH final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (std::abs(phiValue) > TOLERANCE || + std::abs(lambdaValue - std::numbers::pi) > TOLERANCE) { + return failure(); + } + + auto hOp = rewriter.create(op.getLoc(), op.getInputQubit(0)); + rewriter.replaceOp(op, hOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U2(-pi / 2, pi / 2) with RX(pi / 2). + */ +struct ReplaceU2WithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (std::abs(phiValue + (std::numbers::pi / 2.0)) > TOLERANCE || + std::abs(lambdaValue - (std::numbers::pi / 2.0)) > TOLERANCE) { + return failure(); + } + + auto rxOp = rewriter.create(op.getLoc(), op.getInputQubit(0), + std::numbers::pi / 2.0); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U2(0, 0) with RY(pi / 2). + */ +struct ReplaceU2WithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (std::abs(phiValue) > TOLERANCE || std::abs(lambdaValue) > TOLERANCE) { + return failure(); + } + + auto ryOp = rewriter.create(op.getLoc(), op.getInputQubit(0), + std::numbers::pi / 2.0); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + +} // namespace + +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& phi, + const std::variant& lambda) { + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, phiOperand, lambdaOperand); +} + +void U2Op::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp new file mode 100644 index 0000000000..422c785bf4 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Replace U(0, 0, lambda) with P(lambda). + */ +struct ReplaceUWithP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto theta = UOp::getStaticParameter(op.getTheta()); + const auto phi = UOp::getStaticParameter(op.getPhi()); + if (!theta || !phi) { + return failure(); + } + + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + if (std::abs(thetaValue) > TOLERANCE || std::abs(phiValue) > TOLERANCE) { + return failure(); + } + + auto pOp = + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getLambda()); + rewriter.replaceOp(op, pOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U(theta, -pi / 2, pi / 2) with RX(theta). + */ +struct ReplaceUWithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto phi = UOp::getStaticParameter(op.getPhi()); + const auto lambda = UOp::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (std::abs(phiValue + (std::numbers::pi / 2.0)) > TOLERANCE || + std::abs(lambdaValue - (std::numbers::pi / 2.0)) > TOLERANCE) { + return failure(); + } + + auto rxOp = + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U(theta, 0, 0) with RY(theta). + */ +struct ReplaceUWithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto phi = UOp::getStaticParameter(op.getPhi()); + const auto lambda = UOp::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (std::abs(phiValue) > TOLERANCE || std::abs(lambdaValue) > TOLERANCE) { + return failure(); + } + + auto ryOp = + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + +} // namespace + +void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + const std::variant& theta, + const std::variant& phi, + const std::variant& lambda) { + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); +} + +void UOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp new file mode 100644 index 0000000000..8ae30880be --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent X operations on the same qubit. + */ +struct RemoveSubsequentX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void XOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp new file mode 100644 index 0000000000..2ac73ec8bf --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent XXMinusYY operations on the same qubits by adding + * their thetas. + */ +struct MergeSubsequentXXMinusYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XXMinusYYOp op, + PatternRewriter& rewriter) const override { + auto prevOp = op.getInputQubit(0).getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { + return failure(); + } + + // Confirm betas are equal + auto beta = XXMinusYYOp::getStaticParameter(op.getBeta()); + auto prevBeta = XXMinusYYOp::getStaticParameter(prevOp.getBeta()); + if (beta && prevBeta) { + if (std::abs(beta.getValueAsDouble() - prevBeta.getValueAsDouble()) > + TOLERANCE) { + return failure(); + } + } else if (op.getBeta() != prevOp.getBeta()) { + return failure(); + } + + // Compute and set new theta, which has index 2 + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); + return success(); + } +}; + +} // namespace + +void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, + Value qubit0In, Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); +} + +void XXMinusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp new file mode 100644 index 0000000000..edae5d24f3 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent XXPlusYY operations on the same qubits by adding + * their thetas. + */ +struct MergeSubsequentXXPlusYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XXPlusYYOp op, + PatternRewriter& rewriter) const override { + auto prevOp = op.getInputQubit(0).getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { + return failure(); + } + + // Confirm betas are equal + auto beta = XXPlusYYOp::getStaticParameter(op.getBeta()); + auto prevBeta = XXPlusYYOp::getStaticParameter(prevOp.getBeta()); + if (beta && prevBeta) { + if (std::abs(beta.getValueAsDouble() - prevBeta.getValueAsDouble()) > + TOLERANCE) { + return failure(); + } + } else if (op.getBeta() != prevOp.getBeta()) { + return failure(); + } + + // Compute and set new theta, which has index 2 + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); + + return success(); + } +}; + +} // namespace + +void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, + Value qubit0In, Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); +} + +void XXPlusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp new file mode 100644 index 0000000000..b498928b4e --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent Y operations on the same qubit. + */ +struct RemoveSubsequentY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(YOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void YOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp new file mode 100644 index 0000000000..a8d86e7082 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove subsequent Z operations on the same qubit. + */ +struct RemoveSubsequentZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ZOp op, + PatternRewriter& rewriter) const override { + return removeInversePairOneTargetZeroParameter(op, rewriter); + } +}; + +} // namespace + +void ZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp new file mode 100644 index 0000000000..c5307f94a2 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" // IWYU pragma: associated + +// The following headers are needed for some template instantiations. +// IWYU pragma: begin_keep +#include +#include +#include +#include +// IWYU pragma: end_keep + +using namespace mlir; +using namespace mlir::qco; + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QCO/IR/QCOOpsDialect.cpp.inc" + +void QCODialect::initialize() { + // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" + >(); + + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" + >(); +} + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/QCO/IR/QCOInterfaces.cpp.inc" + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp new file mode 100644 index 0000000000..6a2f36a259 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include + +using namespace mlir; +using namespace mlir::qco; + +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp new file mode 100644 index 0000000000..cc0eeacc87 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +/** + * @brief Remove matching allocation and deallocation pairs without operations + * between them. + */ +struct RemoveAllocDeallocPair final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(DeallocOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + auto allocOp = op.getQubit().getDefiningOp(); + if (!allocOp) { + return failure(); + } + + // Remove the AllocOp and the DeallocOp + rewriter.eraseOp(op); + rewriter.eraseOp(allocOp); + return success(); + } +}; + +} // namespace + +void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt new file mode 100644 index 0000000000..aab1a2caad --- /dev/null +++ b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRQIRProgramBuilder + QIRProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRLLVMDialect + MLIRQIRUtils + MLIRIR + MLIRSupport) + +# collect header files +file(GLOB_RECURSE QIR_BUILDER_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRQIRProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${QIR_BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp new file mode 100644 index 0000000000..373ebce5ca --- /dev/null +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +QIRProgramBuilder::QIRProgramBuilder(MLIRContext* context) + : builder(context), + module(builder.create(UnknownLoc::get(context))), + loc(UnknownLoc::get(context)) { + builder.getContext()->loadDialect(); +} + +void QIRProgramBuilder::initialize() { + // Set insertion point to the module body + builder.setInsertionPointToStart(module.getBody()); + + // Create main function: () -> i64 + auto funcType = LLVM::LLVMFunctionType::get(builder.getI64Type(), {}); + mainFunc = builder.create(loc, "main", funcType); + + // Add entry_point attribute to identify the main function + auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); + mainFunc->setAttr("passthrough", + ArrayAttr::get(builder.getContext(), {entryPointAttr})); + + // Create the 4-block structure for QIR Base Profile + entryBlock = mainFunc.addEntryBlock(builder); + bodyBlock = mainFunc.addBlock(); + measurementsBlock = mainFunc.addBlock(); + outputBlock = mainFunc.addBlock(); + + // Create exit code constant in entry block (where constants belong) and add + // QIR initialization call in entry block (after exit code constant) + builder.setInsertionPointToStart(entryBlock); + auto zeroOp = builder.create( + loc, LLVM::LLVMPointerType::get(builder.getContext())); + exitCode = + builder.create(loc, builder.getI64IntegerAttr(0)); + const auto initType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto initFunc = + getOrCreateFunctionDeclaration(builder, module, QIR_INITIALIZE, initType); + builder.create(loc, initFunc, ValueRange{zeroOp.getResult()}); + + // Add unconditional branches between blocks + builder.setInsertionPointToEnd(entryBlock); + builder.create(loc, bodyBlock); + + builder.setInsertionPointToEnd(bodyBlock); + builder.create(loc, measurementsBlock); + + builder.setInsertionPointToEnd(measurementsBlock); + builder.create(loc, outputBlock); + + // Return the exit code (success) in output block + builder.setInsertionPointToEnd(outputBlock); + builder.create(loc, ValueRange{exitCode.getResult()}); + + // Set insertion point to body block for user operations + builder.setInsertionPointToStart(bodyBlock); +} + +Value QIRProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + + // Check cache + Value val{}; + if (const auto it = ptrCache.find(index); it != ptrCache.end()) { + val = it->second; + } else { + val = createPointerFromIndex(builder, loc, index); + // Cache for reuse + ptrCache[index] = val; + } + + // Update qubit count + if (std::cmp_greater_equal(index, metadata_.numQubits)) { + metadata_.numQubits = static_cast(index) + 1; + } + + return val; +} + +SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + SmallVector qubits; + qubits.reserve(size); + + for (int64_t i = 0; i < size; ++i) { + qubits.push_back(staticQubit(static_cast(metadata_.numQubits))); + } + + return qubits; +} + +QIRProgramBuilder::ClassicalRegister +QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, + const std::string& name) { + checkFinalized(); + + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + const auto numResults = static_cast(metadata_.numResults); + for (int64_t i = 0; i < size; ++i) { + Value val{}; + if (const auto it = ptrCache.find(numResults + i); it != ptrCache.end()) { + val = it->second; + } else { + val = createPointerFromIndex(builder, loc, numResults + i); + // Cache for reuse + ptrCache[numResults + i] = val; + } + registerResultMap.insert({{stringSaver.save(name), i}, val}); + } + metadata_.numResults += size; + return {.name = name, .size = size}; +} + +Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { + checkFinalized(); + + if (resultIndex < 0) { + llvm::reportFatalUsageError("Result index must be non-negative"); + } + + // Choose a safe default register name + std::string defaultRegName = "c"; + if (llvm::any_of(registerResultMap, [](const auto& entry) { + return entry.first.first == "c"; + })) { + defaultRegName = "__unnamed__"; + } + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + const auto key = std::make_pair(defaultRegName, resultIndex); + if (const auto it = registerResultMap.find(key); + it != registerResultMap.end()) { + return it->second; + } + + Value resultValue{}; + if (const auto it = ptrCache.find(resultIndex); it != ptrCache.end()) { + resultValue = it->second; + } else { + resultValue = createPointerFromIndex(builder, loc, resultIndex); + ptrCache[resultIndex] = resultValue; + registerResultMap.insert({key, resultValue}); + } + + // Update result count + if (std::cmp_greater_equal(resultIndex, metadata_.numResults)) { + metadata_.numResults = static_cast(resultIndex) + 1; + } + + // Create mz call + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto mzSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + auto mzDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); + builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); + + return resultValue; +} + +QIRProgramBuilder& QIRProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + // Check if we already have a result pointer for this register slot + const auto& registerName = bit.registerName; + const auto registerIndex = bit.registerIndex; + const auto key = std::make_pair(registerName, registerIndex); + if (!registerResultMap.contains(key)) { + llvm::reportFatalInternalError("Result pointer not found"); + } + const auto resultValue = registerResultMap.at(key); + + // Create mz call + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto mzSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + auto mzDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); + builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); + + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::reset(Value qubit) { + checkFinalized(); + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + // Create reset call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_RESET, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +void QIRProgramBuilder::createCallOp( + const SmallVector>& parameters, + ValueRange controls, const SmallVector& targets, StringRef fnName) { + checkFinalized(); + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert constants in entry block + builder.setInsertionPoint(entryBlock->getTerminator()); + + SmallVector parameterOperands; + parameterOperands.reserve(parameters.size()); + for (const auto& parameter : parameters) { + Value parameterOperand; + if (std::holds_alternative(parameter)) { + parameterOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter))) + .getResult(); + } else { + parameterOperand = std::get(parameter); + } + parameterOperands.push_back(parameterOperand); + } + + // Save current insertion point + const OpBuilder::InsertionGuard entryBlockGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(parameters.size() + controls.size() + targets.size()); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto floatType = Float64Type::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + for (size_t i = 0; i < targets.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add parameter types + for (size_t i = 0; i < parameters.size(); ++i) { + argumentTypes.push_back(floatType); + } + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(parameters.size() + controls.size() + targets.size()); + operands.append(controls.begin(), controls.end()); + operands.append(targets.begin(), targets.end()); + operands.append(parameterOperands.begin(), parameterOperands.end()); + + builder.create(loc, fnDecl, operands); +} + +// GPhaseOp + +QIRProgramBuilder& +QIRProgramBuilder::gphase(const std::variant& theta) { + createCallOp({theta}, {}, {}, QIR_GPHASE); + return *this; +} + +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(Value qubit) { \ + createCallOp({}, {}, {qubit}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(Value control, \ + Value target) { \ + createCallOp({}, {control}, {target}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL(ValueRange controls, \ + Value target) { \ + createCallOp({}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(I, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(X, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(H, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(S, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SDG, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(T, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TDG, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SX, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM), Value qubit) { \ + createCallOp({PARAM}, {}, {qubit}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM), Value control, \ + Value target) { \ + createCallOp({PARAM}, {control}, {target}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target) { \ + createCallOp({PARAM}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RX, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RY, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZ, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit) { \ + createCallOp({PARAM1, PARAM2}, {}, {qubit}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target) { \ + createCallOp({PARAM1, PARAM2}, {control}, {target}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target) { \ + createCallOp({PARAM1, PARAM2}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(R, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2, PARAM3) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value qubit) { \ + createCallOp({PARAM1, PARAM2, PARAM3}, {}, {qubit}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ + createCallOp({PARAM1, PARAM2, PARAM3}, {control}, {target}, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ + createCallOp({PARAM1, PARAM2, PARAM3}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(Value target0, \ + Value target1) { \ + createCallOp({}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + Value control, Value target0, Value target1) { \ + createCallOp({}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + ValueRange controls, Value target0, Value target1) { \ + createCallOp({}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAP, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ISWAP, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCX, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM), Value target0, \ + Value target1) { \ + createCallOp({PARAM}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM), Value control, Value target0, \ + Value target1) { \ + createCallOp({PARAM}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target0, Value target1) { \ + createCallOp({PARAM}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYY, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZX, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value target0, \ + Value target1) { \ + createCallOp({PARAM1, PARAM2}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target0, Value target1) { \ + createCallOp({PARAM1, PARAM2}, {control}, {target0, target1}, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target0, Value target1) { \ + createCallOp({PARAM1, PARAM2}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPLUSYY, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMINUSYY, xx_minus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + +void QIRProgramBuilder::checkFinalized() const { + if (isFinalized) { + llvm::reportFatalUsageError( + "QIRProgramBuilder instance has been finalized"); + } +} + +void QIRProgramBuilder::generateOutputRecording() { + if (registerResultMap.empty()) { + return; // No measurements to record + } + + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Insert in output block (before return) + builder.setInsertionPoint(outputBlock->getTerminator()); + + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + + // Group measurements by register + llvm::StringMap>> registerGroups; + for (const auto& [key, resultPtr] : registerResultMap) { + const auto& [regName, regIdx] = key; + registerGroups[regName].emplace_back(regIdx, resultPtr); + } + + // Sort registers by name for deterministic output + SmallVector>>> + sortedRegisters; + for (auto& [name, measurements] : registerGroups) { + sortedRegisters.emplace_back(name, std::move(measurements)); + } + sort(sortedRegisters, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Create array_record_output call + const auto arrayRecordSig = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(builder.getContext()), + {builder.getI64Type(), ptrType}); + const auto arrayRecordDecl = getOrCreateFunctionDeclaration( + builder, module, QIR_ARRAY_RECORD_OUTPUT, arrayRecordSig); + + // Create result_record_output calls for each measurement + const auto resultRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + const auto resultRecordDecl = getOrCreateFunctionDeclaration( + builder, module, QIR_RECORD_OUTPUT, resultRecordSig); + + // Generate output recording for each register + for (auto& [registerName, measurements] : sortedRegisters) { + // Sort measurements by register index + sort(measurements, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + const auto arraySize = measurements.size(); + auto arrayLabelOp = createResultLabel(builder, module, registerName); + auto arraySizeConst = builder.create( + loc, builder.getI64IntegerAttr(static_cast(arraySize))); + + builder.create( + loc, arrayRecordDecl, + ValueRange{arraySizeConst.getResult(), arrayLabelOp.getResult()}); + + for (const auto [regIdx, resultPtr] : measurements) { + // Create label for result: "{registerName}{regIdx}r" + const std::string resultLabel = + registerName + std::to_string(regIdx) + "r"; + auto resultLabelOp = createResultLabel(builder, module, resultLabel); + + builder.create( + loc, resultRecordDecl, + ValueRange{resultPtr, resultLabelOp.getResult()}); + } + } +} + +OwningOpRef QIRProgramBuilder::finalize() { + checkFinalized(); + + // Generate output recording in the output block + generateOutputRecording(); + + setQIRAttributes(mainFunc, metadata_); + + isFinalized = true; + + return module; +} + +} // namespace mlir::qir diff --git a/mlir/lib/Dialect/QIR/CMakeLists.txt b/mlir/lib/Dialect/QIR/CMakeLists.txt new file mode 100644 index 0000000000..319b90b05a --- /dev/null +++ b/mlir/lib/Dialect/QIR/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(Utils) +add_subdirectory(Builder) diff --git a/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt b/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt new file mode 100644 index 0000000000..4280d86d1e --- /dev/null +++ b/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB QIR_UTILS_SOURCES *.cpp) + +add_mlir_library(MLIRQIRUtils ${QIR_UTILS_SOURCES} LINK_LIBS PUBLIC MLIRLLVMDialect MLIRIR) + +target_include_directories( + MLIRQIRUtils PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Utils/QIRUtils.h) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp new file mode 100644 index 0000000000..15ec2f2b3d --- /dev/null +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +LLVM::LLVMFuncOp getMainFunction(Operation* op) { + auto module = dyn_cast(op); + if (!module) { + module = op->getParentOfType(); + } + if (!module) { + return nullptr; + } + + // Search for function with entry_point attribute + for (const auto funcOp : module.getOps()) { + auto passthrough = funcOp->getAttrOfType("passthrough"); + if (!passthrough) { + continue; + } + if (llvm::any_of(passthrough, [](Attribute attr) { + const auto strAttr = dyn_cast(attr); + return strAttr && strAttr.getValue() == "entry_point"; + })) { + return funcOp; + } + } + return nullptr; +} + +void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata) { + OpBuilder builder(main.getBody()); + llvm::SmallVector attributes; + + // Core QIR attributes + attributes.emplace_back(builder.getStringAttr("entry_point")); + attributes.emplace_back( + builder.getStrArrayAttr({"output_labeling_schema", "labeled"})); + attributes.emplace_back( + builder.getStrArrayAttr({"qir_profiles", "base_profile"})); + + // Resource requirements + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_qubits", std::to_string(metadata.numQubits)})); + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_results", std::to_string(metadata.numResults)})); + + // QIR version (Base Profile spec requires version 2.0) + attributes.emplace_back(builder.getStrArrayAttr({"qir_major_version", "2"})); + attributes.emplace_back(builder.getStrArrayAttr({"qir_minor_version", "0"})); + + // Management model + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_qubit_management", + metadata.useDynamicQubit ? "true" : "false"})); + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_result_management", + metadata.useDynamicResult ? "true" : "false"})); + + main->setAttr("passthrough", builder.getArrayAttr(attributes)); +} + +LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, + Operation* op, StringRef fnName, + Type fnType) { + // Check if the function already exists + auto* fnDecl = + SymbolTable::lookupNearestSymbolFrom(op, builder.getStringAttr(fnName)); + + if (fnDecl == nullptr) { + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + // Create the declaration at the end of the module + auto module = dyn_cast(op); + if (!module) { + module = op->getParentOfType(); + } + if (!module) { + llvm::reportFatalInternalError("Module not found"); + } + builder.setInsertionPointToEnd(module.getBody()); + + fnDecl = builder.create(op->getLoc(), fnName, fnType); + + // Add irreversible attribute to irreversible quantum operations + if (fnName == QIR_MEASURE || fnName == QIR_RESET) { + fnDecl->setAttr("passthrough", builder.getStrArrayAttr({"irreversible"})); + } + } + + return cast(fnDecl); +} + +LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, + const StringRef label, + const StringRef symbolPrefix) { + // Save current insertion point + const OpBuilder::InsertionGuard guard(builder); + + auto module = dyn_cast(op); + if (!module) { + module = op->getParentOfType(); + } + if (!module) { + llvm::reportFatalInternalError("Module not found"); + } + + const auto symbolName = + builder.getStringAttr((symbolPrefix + "_" + label).str()); + + if (!module.lookupSymbol(symbolName)) { + const auto llvmArrayType = LLVM::LLVMArrayType::get( + builder.getIntegerType(8), static_cast(label.size() + 1)); + const auto stringInitializer = builder.getStringAttr(label.str() + '\0'); + + // Create the declaration at the start of the module + builder.setInsertionPointToStart(module.getBody()); + + const auto globalOp = builder.create( + op->getLoc(), llvmArrayType, /*isConstant=*/true, + LLVM::Linkage::Internal, symbolName, stringInitializer); + globalOp->setAttr("addr_space", builder.getI32IntegerAttr(0)); + globalOp->setAttr("dso_local", builder.getUnitAttr()); + } + + // Create AddressOfOp + // Shall be added to the first block of the `main` function in the module + auto main = getMainFunction(op); + if (!main) { + llvm::reportFatalInternalError("Main function not found"); + } + auto& firstBlock = *(main.getBlocks().begin()); + builder.setInsertionPointToStart(&firstBlock); + + const auto addressOfOp = builder.create( + op->getLoc(), LLVM::LLVMPointerType::get(builder.getContext()), + symbolName); + + return addressOfOp; +} + +Value createPointerFromIndex(OpBuilder& builder, const Location loc, + const int64_t index) { + auto constantOp = + builder.create(loc, builder.getI64IntegerAttr(index)); + auto intToPtrOp = builder.create( + loc, LLVM::LLVMPointerType::get(builder.getContext()), + constantOp.getResult()); + return intToPtrOp.getResult(); +} + +} // namespace mlir::qir diff --git a/mlir/lib/Support/CMakeLists.txt b/mlir/lib/Support/CMakeLists.txt new file mode 100644 index 0000000000..cd368aad5e --- /dev/null +++ b/mlir/lib/Support/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT + +add_mlir_library( + MLIRSupportMQT + PrettyPrinting.cpp + ADDITIONAL_HEADER_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support + LINK_LIBS + PUBLIC + MLIRSupport + MLIRIR) + +# add library alias +add_library(MQT::MLIRSupport ALIAS MLIRSupportMQT) + +# collect header files +file(GLOB_RECURSE SUPPORT_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support/*.h") +target_sources(MLIRSupportMQT PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${SUPPORT_HEADERS_SOURCE}) diff --git a/mlir/lib/Support/PrettyPrinting.cpp b/mlir/lib/Support/PrettyPrinting.cpp new file mode 100644 index 0000000000..edc2f7eed3 --- /dev/null +++ b/mlir/lib/Support/PrettyPrinting.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Support/PrettyPrinting.h" + +#include +#include +#include +#include +#include + +namespace mlir { + +/** + * @brief Trim trailing whitespace from a string + */ +static std::string trimTrailingWhitespace(const std::string& str) { + const size_t end = str.find_last_not_of(" \t\r\n"); + return (end == std::string::npos) ? "" : str.substr(0, end + 1); +} + +constexpr auto TOTAL_WIDTH = 120; +constexpr auto BORDER_WIDTH = 2; // "║ " on each side + +int calculateDisplayWidth(const std::string& str) { + auto displayWidth = 0; + for (size_t i = 0; i < str.size();) { + if (const unsigned char c = str[i]; (c & 0x80) == 0) { + // ASCII character (1 byte) + displayWidth++; + i++; + } else if ((c & 0xE0) == 0xC0) { + // 2-byte UTF-8 character + displayWidth++; + i += 2; + } else if ((c & 0xF0) == 0xE0) { + // 3-byte UTF-8 character (like → and ✓) + displayWidth++; + i += 3; + } else if ((c & 0xF8) == 0xF0) { + // 4-byte UTF-8 character + displayWidth += 2; // Most emojis take 2 display columns + i += 4; + } else { + // Invalid UTF-8, skip + i++; + } + } + return displayWidth; +} + +std::vector wrapLine(const std::string& line, const int maxWidth, + const int indent) { + std::vector wrapped; + + if (line.empty()) { + wrapped.emplace_back(""); + return wrapped; + } + + // Detect leading whitespace (indentation) in the original line + size_t leadingSpaces = 0; + for (const char c : line) { + if (c == ' ') { + leadingSpaces++; + } else if (c == '\t') { + leadingSpaces += 4; // Count tabs as 4 spaces + } else { + break; + } + } + + // Extract the content without leading whitespace + std::string content = line.substr(line.find_first_not_of(" \t")); + if (content.empty()) { + wrapped.emplace_back(line); + return wrapped; + } + + // Calculate available width accounting for indentation and wrap indicators + // First line: original indent + content + // Continuation lines: "↳ " (2 chars) + same indent + content + const int firstLineWidth = + maxWidth - indent - static_cast(leadingSpaces); + const int contLineWidth = + maxWidth - indent - static_cast(leadingSpaces) - 2; // "↳ " + + if (firstLineWidth <= 10 || contLineWidth <= 10) { + // Not enough space to wrap intelligently, just return original + wrapped.emplace_back(line); + return wrapped; + } + + std::string currentLine; + std::string currentWord; + auto currentWidth = 0; + auto isFirstLine = true; + auto addWord = [&](const std::string& word) { + const int wordWidth = calculateDisplayWidth(word); + const int spaceWidth = currentLine.empty() ? 0 : 1; + const int effectiveWidth = isFirstLine ? firstLineWidth : contLineWidth; + + if (currentWidth + spaceWidth + wordWidth <= effectiveWidth) { + // Word fits on current line + if (!currentLine.empty()) { + currentLine += ' '; + currentWidth++; + } + currentLine += word; + currentWidth += wordWidth; + return true; + } + return false; + }; + + // Process the content word by word + for (size_t i = 0; i < content.size(); ++i) { + const char c = content[i]; + + if (c == ' ' || c == '\t') { + // End of word - try to add it to current line + if (!currentWord.empty()) { + if (!addWord(currentWord)) { + // Word doesn't fit - finalize current line and start new one + if (!currentLine.empty()) { + // Add wrap indicator to the end + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + if (!isFirstLine || (i < content.size() - 1)) { + lineWithIndent += " →"; + } + wrapped.push_back(lineWithIndent); + } + + // Start new continuation line + currentLine = currentWord; + currentWidth = calculateDisplayWidth(currentWord); + isFirstLine = false; + } + currentWord.clear(); + } + } else { + currentWord += c; + } + } + + // Add remaining word + if (!currentWord.empty()) { + if (!addWord(currentWord)) { + // Finalize current line + if (!currentLine.empty()) { + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine + " →"; + wrapped.push_back(lineWithIndent); + } + + // Add word on new line + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent = "↳ " + lineWithIndent + currentWord; + wrapped.push_back(lineWithIndent); + isFirstLine = false; + } else { + // Word fit, add the final line + if (!currentLine.empty()) { + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + wrapped.push_back(lineWithIndent); + } + } + } else if (!currentLine.empty()) { + // Add the final line + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + wrapped.push_back(lineWithIndent); + } + + // If we didn't wrap anything, return the original line + if (wrapped.empty()) { + wrapped.push_back(line); + } else if (wrapped.size() > 1) { + // Add continuation indicator to all but the first and last lines + for (size_t i = 1; i < wrapped.size(); ++i) { + if (wrapped[i].find("↳") == std::string::npos) { + const std::string indentStr(leadingSpaces, ' '); + wrapped[i] = "↳ " + indentStr + wrapped[i].substr(leadingSpaces); + } + } + } + + return wrapped; +} + +void printBoxTop(llvm::raw_ostream& os) { + os << "╔"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╗\n"; +} + +void printBoxMiddle(llvm::raw_ostream& os) { + os << "╠"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╣\n"; +} + +void printBoxBottom(llvm::raw_ostream& os) { + os << "╚"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╝\n"; +} + +void printBoxLine(const std::string& text, const int indent, + llvm::raw_ostream& os) { + // Content width = Total width - left border (2 chars) - right border (2 + // chars) + constexpr int contentWidth = + TOTAL_WIDTH - (2 * BORDER_WIDTH); // "║ " and " ║" + + // Trim trailing whitespace before processing + const std::string trimmedText = trimTrailingWhitespace(text); + + // Wrap the line if needed + for (const auto wrappedLines = wrapLine(trimmedText, contentWidth, indent); + const auto& line : wrappedLines) { + const int displayWidth = calculateDisplayWidth(line); + const int padding = contentWidth - indent - displayWidth; + + os << "║ "; + for (auto i = 0; i < indent; ++i) { + os << " "; + } + os << line; + for (auto i = 0; i < padding; ++i) { + os << " "; + } + os << " ║\n"; + } +} + +void printBoxText(const std::string& text, const int indent, + llvm::raw_ostream& os) { + // Trim trailing newlines from the entire text + std::string trimmedText = text; + while (!trimmedText.empty() && + (trimmedText.back() == '\n' || trimmedText.back() == '\r')) { + trimmedText.pop_back(); + } + + std::istringstream stream(trimmedText); + std::string line; + + while (std::getline(stream, line)) { + printBoxLine(line, indent, os); + } +} + +} // namespace mlir diff --git a/mlir/tools/CMakeLists.txt b/mlir/tools/CMakeLists.txt index e845e91ec8..fc2782a9df 100644 --- a/mlir/tools/CMakeLists.txt +++ b/mlir/tools/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(quantum-opt) +add_subdirectory(mqt-cc) diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt new file mode 100644 index 0000000000..bae241ddd0 --- /dev/null +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Build the compiler driver executable +add_mlir_tool(mqt-cc mqt-cc.cpp DEPENDS MQTCompilerPipeline SUPPORT_PLUGINS) +target_compile_options(mqt-cc PRIVATE -fexceptions) +target_link_libraries( + mqt-cc + PRIVATE MQTCompilerPipeline + # Required for parsing .mlir files + MLIRParser + # Required for file I/O + MLIRSupport) +llvm_update_compile_flags(mqt-cc) +mlir_check_all_link_libraries(mqt-cc) +export_executable_symbols_for_plugins(mqt-cc) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp new file mode 100644 index 0000000000..5ad5f67395 --- /dev/null +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace mlir; + +namespace { + +// Command-line options +const cl::opt INPUT_FILENAME(cl::Positional, + cl::desc(""), + cl::init("-")); + +const cl::opt OUTPUT_FILENAME("o", cl::desc("Output filename"), + cl::value_desc("filename"), + cl::init("-")); + +const cl::opt CONVERT_TO_QIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); + +const cl::opt RECORD_INTERMEDIATES( + "record-intermediates", + cl::desc("Record intermediate IR after each compiler stage"), + cl::init(false)); + +const cl::opt ENABLE_TIMING("mlir-timing", + cl::desc("Enable pass timing statistics"), + cl::init(false)); + +const cl::opt ENABLE_STATISTICS("mlir-statistics", + cl::desc("Enable pass statistics"), + cl::init(false)); + +const cl::opt + PRINT_IR_AFTER_ALL_STAGES("mlir-print-ir-after-all-stages", + cl::desc("Print IR after each compiler stage"), + cl::init(false)); + +} // namespace + +/** + * @brief Load and parse a .mlir file + */ +static OwningOpRef loadMLIRFile(StringRef filename, + MLIRContext* context) { + // Set up the input file + std::string errorMessage; + auto file = openInputFile(filename, &errorMessage); + if (!file) { + errs() << errorMessage << "\n"; + return nullptr; + } + + // Parse the input MLIR + SourceMgr sourceMgr; + sourceMgr.AddNewSourceBuffer(std::move(file), SMLoc()); + return parseSourceFile(sourceMgr, context); +} + +/** + * @brief Write the module to the output file + */ +static mlir::LogicalResult writeOutput(ModuleOp module, StringRef filename) { + std::string errorMessage; + const auto output = openOutputFile(filename, &errorMessage); + if (!output) { + errs() << errorMessage << "\n"; + return mlir::failure(); + } + + module.print(output->os()); + output->keep(); + return mlir::success(); +} + +int main(int argc, char** argv) { + const InitLLVM y(argc, argv); + + // Parse command-line options + cl::ParseCommandLineOptions(argc, argv, "MQT Core Compiler Driver\n"); + + // Set up MLIR context with all required dialects + DialectRegistry registry; + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + + MLIRContext context(registry); + context.loadAllAvailableDialects(); + + // Load the input .mlir file + const auto module = loadMLIRFile(INPUT_FILENAME, &context); + if (!module) { + errs() << "Failed to load input file: " << INPUT_FILENAME << "\n"; + return 1; + } + + // Configure the compiler pipeline + QuantumCompilerConfig config; + config.convertToQIR = CONVERT_TO_QIR; + config.recordIntermediates = RECORD_INTERMEDIATES; + config.enableTiming = ENABLE_TIMING; + config.enableStatistics = ENABLE_STATISTICS; + config.printIRAfterAllStages = PRINT_IR_AFTER_ALL_STAGES; + + // Run the compilation pipeline + CompilationRecord record; + if (const QuantumCompilerPipeline pipeline(config); + pipeline + .runPipeline(module.get(), RECORD_INTERMEDIATES ? &record : nullptr) + .failed()) { + errs() << "Compilation pipeline failed\n"; + return 1; + } + + if (RECORD_INTERMEDIATES) { + outs() << "=== Compilation Record ===\n"; + outs() << "After QC Import:\n" << record.afterQCImport << "\n"; + outs() << "After Initial QC Canonicalization:\n" + << record.afterInitialCanon << "\n"; + outs() << "After QC-to-QCO Conversion:\n" + << record.afterQCOConversion << "\n"; + outs() << "After Initial QCO Canonicalization:\n" + << record.afterQCOCanon << "\n"; + outs() << "After Optimization:\n" << record.afterOptimization << "\n"; + outs() << "After Final QCO Canonicalization:\n" + << record.afterOptimizationCanon << "\n"; + outs() << "After QCO-to-QC Conversion:\n" + << record.afterQCConversion << "\n"; + outs() << "After Final QC Canonicalization:\n" + << record.afterQCCanon << "\n"; + outs() << "After QC-to-QIR Conversion:\n" + << record.afterQIRConversion << "\n"; + outs() << "After QIR Canonicalization:\n" << record.afterQIRCanon << "\n"; + } + + // Write the output + if (writeOutput(module.get(), OUTPUT_FILENAME).failed()) { + errs() << "Failed to write output file: " << OUTPUT_FILENAME << "\n"; + return 1; + } + + return 0; +} diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index 46421d9bc1..43ffbbd241 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -6,5 +6,11 @@ # # Licensed under the MIT License -add_subdirectory(translation) add_subdirectory(dialect) +add_subdirectory(pipeline) +add_subdirectory(translation) + +add_custom_target(mqt-core-mlir-unittests) + +add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-compiler-pipeline-test + mqt-core-mlir-translation-test mqt-core-mlir-wireiterator-test) diff --git a/mlir/unittests/dialect/test_wireiterator.cpp b/mlir/unittests/dialect/test_wireiterator.cpp index 562dd4a535..0493872be3 100644 --- a/mlir/unittests/dialect/test_wireiterator.cpp +++ b/mlir/unittests/dialect/test_wireiterator.cpp @@ -33,11 +33,10 @@ using namespace mlir; using namespace mqt::ir::opt; -namespace { /** @returns a module containing the circuit from the "Tackling the Qubit * Mapping Problem for NISQ-Era Quantum Devices" paper by Li et al. */ -OwningOpRef getModule(MLIRContext& ctx) { +static OwningOpRef getModule(MLIRContext& ctx) { const char* ir = R"mlir( module { %0 = mqtopt.allocQubit @@ -74,7 +73,7 @@ module { return parseSourceString(ir, &ctx); } -std::string toString(Operation* op) { +static std::string toString(Operation* op) { std::string opStr; llvm::raw_string_ostream os(opStr); os << *op; @@ -82,14 +81,13 @@ std::string toString(Operation* op) { return opStr; } -void checkOperationEqual(Operation* op, const std::string& expected) { +static void checkOperationEqual(Operation* op, const std::string& expected) { ASSERT_EQ(expected, toString(op)); } -void checkOperationStartsWith(Operation* op, const std::string& prefix) { +static void checkOperationStartsWith(Operation* op, const std::string& prefix) { ASSERT_TRUE(toString(op).starts_with(prefix)); } -} // namespace class WireIteratorTest : public ::testing::Test { protected: diff --git a/mlir/unittests/pipeline/CMakeLists.txt b/mlir/unittests/pipeline/CMakeLists.txt new file mode 100644 index 0000000000..a7b268e84c --- /dev/null +++ b/mlir/unittests/pipeline/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_executable(mqt-core-mlir-compiler-pipeline-test test_compiler_pipeline.cpp) + +target_link_libraries( + mqt-core-mlir-compiler-pipeline-test + PRIVATE GTest::gtest_main + MQTCompilerPipeline + MLIRQCTranslation + MLIRQCProgramBuilder + MLIRQCOProgramBuilder + MLIRQIRProgramBuilder + MLIRParser + MQT::CoreIR) + +gtest_discover_tests(mqt-core-mlir-compiler-pipeline-test) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp new file mode 100644 index 0000000000..70c32a10cc --- /dev/null +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -0,0 +1,3731 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ir/QuantumComputation.hpp" +#include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" +#include "mlir/Support/PrettyPrinting.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace mlir; + +//===----------------------------------------------------------------------===// +// Stage Verification Utility +//===----------------------------------------------------------------------===// + +/// Compute a structural hash for an operation (excluding SSA value identities). +/// This hash is based on operation name, types, and attributes only. +struct OperationStructuralHash { + size_t operator()(Operation* op) const { + size_t hash = llvm::hash_value(op->getName().getStringRef()); + + // Hash result types + for (auto type : op->getResultTypes()) { + hash = llvm::hash_combine(hash, type.getAsOpaquePointer()); + } + + // Hash operand types (not values) + for (auto operand : op->getOperands()) { + hash = llvm::hash_combine(hash, operand.getType().getAsOpaquePointer()); + } + + // Hash attributes + // for (const auto& attr : op->getAttrDictionary()) { + // hash = llvm::hash_combine(hash, attr.getName().str()); + // hash = llvm::hash_combine(hash, attr.getValue().getAsOpaquePointer()); + // } + + return hash; + } +}; + +/// Check if two operations are structurally equivalent (excluding SSA value +/// identities). +struct OperationStructuralEquality { + bool operator()(Operation* lhs, Operation* rhs) const { + // Check operation name + if (lhs->getName() != rhs->getName()) { + return false; + } + + // Check result types + if (lhs->getResultTypes() != rhs->getResultTypes()) { + return false; + } + + // Check operand types (not values) + auto lhsOperandTypes = lhs->getOperandTypes(); + auto rhsOperandTypes = rhs->getOperandTypes(); + return llvm::equal(lhsOperandTypes, rhsOperandTypes); + + // Note: Attributes are intentionally not checked here to allow relaxed + // comparison. Attributes like function names, parameter names, etc. may + // differ while operations are still structurally equivalent. + } +}; + +/// Map to track value equivalence between two modules. +using ValueEquivalenceMap = llvm::DenseMap; + +} // namespace + +/// Compare two operations for structural equivalence. +/// Updates valueMap to track corresponding SSA values. +static bool areOperationsEquivalent(Operation* lhs, Operation* rhs, + ValueEquivalenceMap& valueMap) { + // Check operation name + if (lhs->getName() != rhs->getName()) { + return false; + } + + // Check arith::ConstantOp + if (auto lhsConst = llvm::dyn_cast(lhs)) { + auto rhsConst = llvm::dyn_cast(rhs); + if (!rhsConst) { + return false; + } + // NOLINTNEXTLINE(cppcoreguidelines-slicing) + if (lhsConst.getValue() != rhsConst.getValue()) { + return false; + } + } + + // Check LLVM::ConstantOp + if (auto lhsConst = llvm::dyn_cast(lhs)) { + auto rhsConst = llvm::dyn_cast(rhs); + if (!rhsConst) { + return false; + } + if (lhsConst.getValue() != rhsConst.getValue()) { + return false; + } + } + + // Check number of operands and results + if (lhs->getNumOperands() != rhs->getNumOperands() || + lhs->getNumResults() != rhs->getNumResults() || + lhs->getNumRegions() != rhs->getNumRegions()) { + return false; + } + + // Note: Attributes are intentionally not checked to allow relaxed comparison + + // Check result types + if (lhs->getResultTypes() != rhs->getResultTypes()) { + return false; + } + + // Check operands according to value mapping + for (auto [lhsOperand, rhsOperand] : + llvm::zip(lhs->getOperands(), rhs->getOperands())) { + if (auto it = valueMap.find(lhsOperand); it != valueMap.end()) { + // Value already mapped, must match + if (it->second != rhsOperand) { + return false; + } + } else { + // Establish new mapping + valueMap[lhsOperand] = rhsOperand; + } + } + + // Update value mapping for results + for (auto [lhsResult, rhsResult] : + llvm::zip(lhs->getResults(), rhs->getResults())) { + valueMap[lhsResult] = rhsResult; + } + + return true; +} + +/// Forward declaration for mutual recursion. +static bool areBlocksEquivalent(Block& lhs, Block& rhs, + ValueEquivalenceMap& valueMap); + +/// Compare two regions for structural equivalence. +static bool areRegionsEquivalent(Region& lhs, Region& rhs, + ValueEquivalenceMap& valueMap) { + if (lhs.getBlocks().size() != rhs.getBlocks().size()) { + return false; + } + + for (auto [lhsBlock, rhsBlock] : llvm::zip(lhs, rhs)) { + if (!areBlocksEquivalent(lhsBlock, rhsBlock, valueMap)) { + return false; + } + } + + return true; +} + +/// Check if an operation has memory effects or control flow side effects +/// that would prevent reordering. +static bool hasOrderingConstraints(Operation* op) { + // Terminators must maintain their position + if (op->hasTrait()) { + return true; + } + + // Symbol-defining operations (like function declarations) can be reordered + if (op->hasTrait() || + llvm::isa(op)) { + return false; + } + + // Check for memory effects that enforce ordering + if (auto memInterface = llvm::dyn_cast(op)) { + llvm::SmallVector effects; + memInterface.getEffects(effects); + + bool hasNonAllocFreeEffects = false; + for (const auto& effect : effects) { + // Allow operations with no effects or pure allocation/free effects + if (!llvm::isa( + effect.getEffect())) { + hasNonAllocFreeEffects = true; + break; + } + } + + if (hasNonAllocFreeEffects) { + return true; + } + } + + return false; +} + +/// Build a dependence graph for operations. +/// Returns a map from each operation to the set of operations it depends on. +static llvm::DenseMap> +buildDependenceGraph(ArrayRef ops) { + llvm::DenseMap> dependsOn; + llvm::DenseMap valueProducers; + + // Build value-to-producer map and dependence relationships + for (Operation* op : ops) { + dependsOn[op] = llvm::DenseSet(); + + // This operation depends on the producers of its operands + for (const auto operand : op->getOperands()) { + if (auto it = valueProducers.find(operand); it != valueProducers.end()) { + dependsOn[op].insert(it->second); + } + } + + // Register this operation as the producer of its results + for (auto result : op->getResults()) { + valueProducers[result] = op; + } + } + + return dependsOn; +} + +/// Partition operations into groups that can be compared as multisets. +/// Operations in the same group are independent and can be reordered. +static std::vector> +partitionIndependentGroups(ArrayRef ops) { + std::vector> groups; + if (ops.empty()) { + return groups; + } + + auto dependsOn = buildDependenceGraph(ops); + const llvm::DenseSet processed; + llvm::SmallVector currentGroup; + + for (auto* op : ops) { + bool dependsOnCurrent = false; + + // Check if this operation depends on any operation in the current group + for (const auto* groupOp : currentGroup) { + if (dependsOn[op].contains(groupOp)) { + dependsOnCurrent = true; + break; + } + } + + // Check if this operation has ordering constraints + const auto hasConstraints = hasOrderingConstraints(op); + + // If it depends on current group or has ordering constraints, + // finalize the current group and start a new one + if (dependsOnCurrent || (hasConstraints && !currentGroup.empty())) { + if (!currentGroup.empty()) { + groups.push_back(std::move(currentGroup)); + currentGroup = {}; + } + } + + currentGroup.push_back(op); + + // If this operation has ordering constraints, finalize the group + if (hasConstraints) { + groups.push_back(std::move(currentGroup)); + currentGroup = {}; + } + } + + // Add any remaining operations + if (!currentGroup.empty()) { + groups.push_back(std::move(currentGroup)); + } + + return groups; +} + +/// Compare two groups of independent operations using multiset equivalence. +static bool areIndependentGroupsEquivalent(ArrayRef lhsOps, + ArrayRef rhsOps) { + if (lhsOps.size() != rhsOps.size()) { + return false; + } + + // Build frequency maps for both groups + std::unordered_map + lhsFrequencyMap; + std::unordered_map + rhsFrequencyMap; + + for (auto* op : lhsOps) { + lhsFrequencyMap[op]++; + } + + for (auto* op : rhsOps) { + rhsFrequencyMap[op]++; + } + + // Check structural equivalence + if (lhsFrequencyMap.size() != rhsFrequencyMap.size()) { + return false; + } + + // NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order) + for (const auto& [lhsOp, lhsCount] : lhsFrequencyMap) { + auto it = rhsFrequencyMap.find(lhsOp); + if (it == rhsFrequencyMap.end() || it->second != lhsCount) { + return false; + } + } + + return true; +} + +/// Compare two blocks for structural equivalence, allowing permutations +/// of independent operations. +static bool areBlocksEquivalent(Block& lhs, Block& rhs, + ValueEquivalenceMap& valueMap) { + // Check block arguments + if (lhs.getNumArguments() != rhs.getNumArguments()) { + return false; + } + + for (auto [lhsArg, rhsArg] : + llvm::zip(lhs.getArguments(), rhs.getArguments())) { + if (lhsArg.getType() != rhsArg.getType()) { + return false; + } + valueMap[lhsArg] = rhsArg; + } + + // Collect all operations + llvm::SmallVector lhsOps; + llvm::SmallVector rhsOps; + + for (Operation& op : lhs) { + lhsOps.push_back(&op); + } + + for (Operation& op : rhs) { + rhsOps.push_back(&op); + } + + if (lhsOps.size() != rhsOps.size()) { + return false; + } + + // Partition operations into independent groups + auto lhsGroups = partitionIndependentGroups(lhsOps); + auto rhsGroups = partitionIndependentGroups(rhsOps); + + if (lhsGroups.size() != rhsGroups.size()) { + return false; + } + + // Compare each group + for (size_t groupIdx = 0; groupIdx < lhsGroups.size(); ++groupIdx) { + auto& lhsGroup = lhsGroups[groupIdx]; + auto& rhsGroup = rhsGroups[groupIdx]; + + if (!areIndependentGroupsEquivalent(lhsGroup, rhsGroup)) { + return false; + } + + // Update value mappings for operations in this group + // We need to match operations and update the value map + // Since they are structurally equivalent, we can match them + // by trying all permutations (for small groups) or use a greedy approach + + // Use a simple greedy matching + llvm::DenseSet matchedRhs; + for (Operation* lhsOp : lhsGroup) { + bool matched = false; + for (Operation* rhsOp : rhsGroup) { + if (matchedRhs.contains(rhsOp)) { + continue; + } + + ValueEquivalenceMap tempMap = valueMap; + if (areOperationsEquivalent(lhsOp, rhsOp, tempMap)) { + valueMap = std::move(tempMap); + matchedRhs.insert(rhsOp); + matched = true; + + // Recursively compare regions + for (auto [lhsRegion, rhsRegion] : + llvm::zip(lhsOp->getRegions(), rhsOp->getRegions())) { + if (!areRegionsEquivalent(lhsRegion, rhsRegion, valueMap)) { + return false; + } + } + break; + } + } + + if (!matched) { + return false; + } + } + } + + return true; +} + +/// Compare two MLIR modules for structural equivalence, allowing permutations +/// of speculatable operations. +static bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { + ValueEquivalenceMap valueMap; + return areRegionsEquivalent(lhs.getBodyRegion(), rhs.getBodyRegion(), + valueMap); +} + +/** + * @brief Verify a stage matches expected module + * + * @param stageName Human-readable stage name for error messages + * @param actualIR String IR from CompilationRecord + * @param expectedModule Expected module to compare against + * @return True if modules match, false otherwise with diagnostic output + */ +[[nodiscard]] static testing::AssertionResult +verify(const std::string& stageName, const std::string& actualIR, + ModuleOp expectedModule) { + // Parse the actual IR string into a ModuleOp + const auto actualModule = + parseSourceString(actualIR, expectedModule.getContext()); + if (!actualModule) { + return testing::AssertionFailure() + << stageName << " failed to parse actual IR"; + } + + if (!areModulesEquivalentWithPermutations(*actualModule, expectedModule)) { + std::ostringstream msg; + msg << stageName << " IR does not match expected structure\n\n"; + + std::string expectedStr; + llvm::raw_string_ostream expectedStream(expectedStr); + expectedModule.print(expectedStream); + expectedStream.flush(); + + msg << "=== EXPECTED IR ===\n" << expectedStr << "\n\n"; + msg << "=== ACTUAL IR ===\n" << actualIR << "\n"; + + return testing::AssertionFailure() << msg.str(); + } + + return testing::AssertionSuccess(); +} + +namespace { + +//===----------------------------------------------------------------------===// +// Stage Expectation Builder +//===----------------------------------------------------------------------===// + +/** + * @brief Helper to build expected IR for multiple stages at once + * + * @details + * Reduces boilerplate by allowing specification of which stages should + * match which expected IR. + */ +struct StageExpectations { + ModuleOp qcImport; + ModuleOp qcoConversion; + ModuleOp optimization; + ModuleOp qcConversion; + ModuleOp qirConversion; +}; + +//===----------------------------------------------------------------------===// +// Base Test Fixture +//===----------------------------------------------------------------------===// + +/** + * @brief Base test fixture for end-to-end compiler pipeline tests + * + * @details + * Provides a configured MLIR context with all necessary dialects loaded + * and utility methods for creating quantum circuits and running the + * compilation pipeline. + */ +class CompilerPipelineTest : public testing::Test { +protected: + std::unique_ptr context; + QuantumCompilerConfig config; + CompilationRecord record; + + OwningOpRef emptyQC; + OwningOpRef emptyQCO; + OwningOpRef emptyQIR; + + void SetUp() override { + // Register all dialects needed for the full compilation pipeline + DialectRegistry registry; + registry.insert(); + + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + + // Enable QIR conversion and recording by default + config.convertToQIR = true; + config.recordIntermediates = true; + config.printIRAfterAllStages = + true; /// TODO: Change back after everything is running + + emptyQC = buildQCIR([](mlir::qc::QCProgramBuilder&) {}); + emptyQCO = buildQCOIR([](qco::QCOProgramBuilder&) {}); + emptyQIR = buildQIR([](qir::QIRProgramBuilder&) {}); + } + + //===--------------------------------------------------------------------===// + // Quantum Circuit Construction and Import + //===--------------------------------------------------------------------===// + + /** + * @brief Pretty print quantum computation with ASCII art borders + * + * @param qc The quantum computation to print + */ + static void + prettyPrintQuantumComputation(const ::qc::QuantumComputation& comp) { + llvm::errs() << "\n"; + printBoxTop(); + + // Print header + printBoxLine("Initial Quantum Computation"); + + printBoxMiddle(); + + // Print internal representation + printBoxLine("Internal Representation:"); + + // Capture the internal representation + std::ostringstream internalRepr; + internalRepr << comp; + const std::string internalStr = internalRepr.str(); + + // Print with line wrapping + printBoxText(internalStr); + + printBoxMiddle(); + + // Print OpenQASM3 representation + printBoxLine("OpenQASM3 Representation:"); + printBoxLine(""); + + const auto qasmStr = comp.toQASM(); + + // Print with line wrapping + printBoxText(qasmStr); + + printBoxBottom(); + llvm::errs().flush(); + } + + /** + * @brief Import a QuantumComputation into QC dialect + */ + [[nodiscard]] OwningOpRef + importQuantumCircuit(const ::qc::QuantumComputation& qc) const { + if (config.printIRAfterAllStages) { + prettyPrintQuantumComputation(qc); + } + return translateQuantumComputationToQC(context.get(), qc); + } + + /** + * @brief Run the compiler pipeline with the current configuration + */ + [[nodiscard]] LogicalResult runPipeline(const ModuleOp module) { + const QuantumCompilerPipeline pipeline(config); + return pipeline.runPipeline(module, &record); + } + + //===--------------------------------------------------------------------===// + // Expected IR Builder Methods + //===--------------------------------------------------------------------===// + + /** + * @brief Run canonicalization + */ + static void runCanonicalizationPasses(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + if (pm.run(module).failed()) { + llvm::errs() << "Failed to run canonicalization passes.\n"; + } + } + + /** + * @brief Build expected QC IR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildQCIR( + const std::function& buildFunc) const { + mlir::qc::QCProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + /** + * @brief Build expected QCO IR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildQCOIR( + const std::function& buildFunc) const { + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + /** + * @brief Build expected QIR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildQIR( + const std::function& buildFunc) const { + qir::QIRProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + //===--------------------------------------------------------------------===// + // Stage Verification Methods + //===--------------------------------------------------------------------===// + + /** + * @brief Verify all stages match their expected IR + * + * @details + * Simplifies test writing by checking all stages with one call. + * Stages without expectations are skipped. + */ + void verifyAllStages(const StageExpectations& expectations) const { + if (expectations.qcImport != nullptr) { + EXPECT_TRUE( + verify("QC Import", record.afterInitialCanon, expectations.qcImport)); + } + + if (expectations.qcoConversion != nullptr) { + EXPECT_TRUE(verify("QCO Conversion", record.afterQCOCanon, + expectations.qcoConversion)); + } + + if (expectations.optimization != nullptr) { + EXPECT_TRUE(verify("Optimization", record.afterOptimizationCanon, + expectations.optimization)); + } + + if (expectations.qcConversion != nullptr) { + EXPECT_TRUE(verify("QC Conversion", record.afterQCCanon, + expectations.qcConversion)); + } + + if (expectations.qirConversion != nullptr) { + EXPECT_TRUE(verify("QIR Conversion", record.afterQIRCanon, + expectations.qirConversion)); + } + } + + void TearDown() override { + // Verify all stages were recorded (basic sanity check) + EXPECT_FALSE(record.afterQCImport.empty()) + << "QC import stage was not recorded"; + EXPECT_FALSE(record.afterInitialCanon.empty()) + << "Initial canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterQCOConversion.empty()) + << "QCO conversion stage was not recorded"; + EXPECT_FALSE(record.afterQCOCanon.empty()) + << "QCO canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterOptimization.empty()) + << "Optimization stage was not recorded"; + EXPECT_FALSE(record.afterOptimizationCanon.empty()) + << "Optimization canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterQCConversion.empty()) + << "QC conversion stage was not recorded"; + EXPECT_FALSE(record.afterQCCanon.empty()) + << "QC canonicalization stage was not recorded"; + + if (config.convertToQIR) { + EXPECT_FALSE(record.afterQIRConversion.empty()) + << "QIR conversion stage was not recorded"; + EXPECT_FALSE(record.afterQIRCanon.empty()) + << "QIR canonicalization stage was not recorded"; + } + } +}; + +//===----------------------------------------------------------------------===// +// Test Cases +//===----------------------------------------------------------------------===// + +// ################################################## +// # Empty Circuit Tests +// ################################################## + +/** + * @brief Test: Empty circuit compilation + * + * @details + * Verifies that an empty circuit compiles correctly through all stages, + * producing empty but valid IR at each stage. + */ +TEST_F(CompilerPipelineTest, EmptyCircuit) { + // Create empty circuit + const ::qc::QuantumComputation comp; + + // Import to QC dialect + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + + // Run compilation + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + // Verify all stages + verifyAllStages({ + .qcImport = emptyQC.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +// ################################################## +// # Quantum Register Allocation Tests +// ################################################## + +/** + * @brief Test: Single qubit register allocation + * + * @details + * Since the register is unused, it should be removed during canonicalization + * in the QCO dialect. + */ +TEST_F(CompilerPipelineTest, SingleQubitRegister) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcExpected = buildQCIR( + [](mlir::qc::QCProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + const auto qcoExpected = buildQCOIR( + [](qco::QCOProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + + verifyAllStages({ + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Multi-qubit register allocation + */ +TEST_F(CompilerPipelineTest, MultiQubitRegister) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcExpected = buildQCIR( + [](mlir::qc::QCProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + const auto qcoExpected = buildQCOIR( + [](qco::QCOProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + + verifyAllStages({ + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Multiple quantum registers + */ +TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.addQubitRegister(3, "aux"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcExpected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + b.allocQubitRegister(2, "q"); + b.allocQubitRegister(3, "aux"); + }); + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + b.allocQubitRegister(2, "q"); + b.allocQubitRegister(3, "aux"); + }); + + verifyAllStages({ + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Large qubit register allocation + */ +TEST_F(CompilerPipelineTest, LargeQubitRegister) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(100, "q"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); +} + +// ################################################## +// # Classical Register Allocation Tests +// ################################################## + +/** + * @brief Test: Single classical bit register + * + * @details + * Since the register is unused, it should be removed during canonicalization. + */ +TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { + ::qc::QuantumComputation comp; + comp.addClassicalRegister(1, "c"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + std::ignore = b.allocClassicalBitRegister(1, "c"); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Multi-bit classical register + * + * @details + * Since the register is unused, it should be removed during canonicalization. + */ +TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { + ::qc::QuantumComputation comp; + comp.addClassicalRegister(5, "c"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + std::ignore = b.allocClassicalBitRegister(5, "c"); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Multiple classical registers + * + * @details + * Since the registers are unused, they should be removed during + * canonicalization. + */ +TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { + ::qc::QuantumComputation comp; + comp.addClassicalRegister(3, "c"); + comp.addClassicalRegister(2, "d"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + std::ignore = b.allocClassicalBitRegister(3, "c"); + std::ignore = b.allocClassicalBitRegister(2, "d"); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Large classical bit register + */ +TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { + ::qc::QuantumComputation comp; + comp.addClassicalRegister(128, "c"); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); +} + +// ################################################## +// # Reset Operation Tests +// ################################################## + +/** + * @brief Test: Single reset in single qubit circuit + * + * @details + * Since the reset directly follows an allocation, it should be removed during + * canonicalization. + */ +TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { + ::qc::QuantumComputation comp(1); + comp.reset(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + }); + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Consecutive reset operations + * + * @details + * Since reset is idempotent, consecutive resets should be reduced to a single + * reset during canonicalization. Since that single reset directly follows an + * allocation, it should be removed as well. + */ +TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { + ::qc::QuantumComputation comp(1); + comp.reset(0); + comp.reset(0); + comp.reset(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + b.reset(q[0]); + b.reset(q[0]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + q[0] = b.reset(q[0]); + q[0] = b.reset(q[0]); + q[0] = b.reset(q[0]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +/** + * @brief Test: Separate resets in two qubit system + */ +TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { + ::qc::QuantumComputation comp(2); + comp.reset(0); + comp.reset(1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + const auto q = b.allocQubitRegister(2, "q"); + b.reset(q[0]); + b.reset(q[1]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2, "q"); + q[0] = b.reset(q[0]); + q[1] = b.reset(q[1]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +// ################################################## +// # Measure Operation Tests +// ################################################## + +/** + * @brief Test: Single measurement to single bit + */ +TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { + ::qc::QuantumComputation comp(1, 1); + comp.measure(0, 0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), + .qirConversion = qirExpected.get(), + }); +} + +/** + * @brief Test: Repeated measurement to same bit + */ +TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { + ::qc::QuantumComputation comp(1, 1); + comp.measure(0, 0); + comp.measure(0, 0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + b.measure(q[0], c[0]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + q[0] = b.measure(q[0], c[0]); + q[0] = b.measure(q[0], c[0]); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + b.measure(q[0], c[0]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), + .qirConversion = qirExpected.get(), + }); +} + +/** + * @brief Test: Repeated measurement on separate bits + */ +TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { + ::qc::QuantumComputation comp(1); + comp.addClassicalRegister(3); + comp.measure(0, 0); + comp.measure(0, 1); + comp.measure(0, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(3); + b.measure(q[0], c[0]); + b.measure(q[0], c[1]); + b.measure(q[0], c[2]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(3); + q[0] = b.measure(q[0], c[0]); + q[0] = b.measure(q[0], c[1]); + q[0] = b.measure(q[0], c[2]); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto& c = b.allocClassicalBitRegister(3); + b.measure(q[0], c[0]); + b.measure(q[0], c[1]); + b.measure(q[0], c[2]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), + .qirConversion = qirExpected.get(), + }); +} + +/** + * @brief Test: Multiple classical registers with measurements + */ +TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { + ::qc::QuantumComputation comp(2); + const auto& c1 = comp.addClassicalRegister(1, "c1"); + const auto& c2 = comp.addClassicalRegister(1, "c2"); + comp.measure(0, c1[0]); + comp.measure(1, c2[0]); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + b.measure(q[0], creg1[0]); + b.measure(q[1], creg2[0]); + }); + + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + q[0] = b.measure(q[0], creg1[0]); + q[1] = b.measure(q[1], creg2[0]); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + b.measure(q[0], creg1[0]); + b.measure(q[1], creg2[0]); + }); + + verifyAllStages({ + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), + .qirConversion = qirExpected.get(), + }); +} + +// ################################################## +// # Temporary Unitary Operation Tests +// ################################################## + +TEST_F(CompilerPipelineTest, GPhase) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.gphase(1.0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.gphase(1.0); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.gphase(1.0); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.gphase(1.0); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, CGPhase) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.cgphase(1.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.p(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCGPhase) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.mcgphase(1.0, {reg[0], reg[1]}); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cp(1.0, reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cp(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cp(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, Id) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.i(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.id(reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.id(reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +TEST_F(CompilerPipelineTest, CId) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ci(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cid(reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cid(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), + .qirConversion = emptyQIR.get(), + }); +} + +TEST_F(CompilerPipelineTest, X) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.x(0); + comp.x(0); + comp.x(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.x(q); + b.x(q); + b.x(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.x(q); + q = b.x(q); + b.x(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.x(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cx(reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cx(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cx(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, CX3) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + comp.cx(1, 0); + comp.cx(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.cx(q0, q1); + std::tie(q1, q0) = b.cx(q1, q0); + std::tie(q0, q1) = b.cx(q0, q1); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcx({0, 1}, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCXNested) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.ctrl(reg[0], [&](OpBuilder& b) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + static_cast(b).cx(reg[1], reg[2]); + }); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCXTrivial) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.mcx({}, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.x(reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, Y) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.y(0); + comp.y(0); + comp.y(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.y(q); + b.y(q); + b.y(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.y(q); + q = b.y(q); + b.y(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.y(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.y(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.y(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, Z) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.z(0); + comp.z(0); + comp.z(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.z(q); + b.z(q); + b.z(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.z(q); + q = b.z(q); + b.z(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.z(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.z(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.z(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, H) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.h(0); + comp.h(0); + comp.h(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.h(q); + b.h(q); + b.h(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.h(q); + q = b.h(q); + b.h(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.h(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, S) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.s(0); + comp.sdg(0); + comp.s(0); + comp.s(0); + comp.s(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + b.sdg(q); + b.s(q); + b.s(q); + b.s(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.s(q); + q = b.sdg(q); + q = b.s(q); + q = b.s(q); + q = b.s(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.z(q); + b.s(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.z(q); + b.s(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.z(q); + b.s(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, Sdg) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sdg(0); + comp.s(0); + comp.sdg(0); + comp.sdg(0); + comp.sdg(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sdg(q); + b.s(q); + b.sdg(q); + b.sdg(q); + b.sdg(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.sdg(q); + q = b.s(q); + q = b.sdg(q); + q = b.sdg(q); + q = b.sdg(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.z(q); + b.sdg(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.z(q); + b.sdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.z(q); + b.sdg(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, T) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.t(0); + comp.tdg(0); + comp.t(0); + comp.t(0); + comp.t(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.t(q); + b.tdg(q); + b.t(q); + b.t(q); + b.t(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.t(q); + q = b.tdg(q); + q = b.t(q); + q = b.t(q); + b.t(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.s(q); + q = b.t(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + b.t(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.s(q); + b.t(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, Tdg) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.tdg(0); + comp.t(0); + comp.tdg(0); + comp.tdg(0); + comp.tdg(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.tdg(q); + b.t(q); + b.tdg(q); + b.tdg(q); + b.tdg(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.tdg(q); + q = b.t(q); + q = b.tdg(q); + q = b.tdg(q); + b.tdg(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.sdg(q); + b.tdg(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sdg(q); + b.tdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.sdg(q); + b.tdg(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, SX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sx(0); + comp.sxdg(0); + comp.sx(0); + comp.sx(0); + comp.sx(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sx(q); + b.sxdg(q); + b.sx(q); + b.sx(q); + b.sx(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.sx(q); + q = b.sxdg(q); + q = b.sx(q); + q = b.sx(q); + q = b.sx(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.x(q); + b.sx(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.x(q); + b.sx(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.x(q); + b.sx(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, SXdg) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sxdg(0); + comp.sx(0); + comp.sxdg(0); + comp.sxdg(0); + comp.sxdg(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sxdg(q); + b.sx(q); + b.sxdg(q); + b.sxdg(q); + b.sxdg(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.sxdg(q); + q = b.sx(q); + q = b.sxdg(q); + q = b.sxdg(q); + q = b.sxdg(q); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.x(q); + b.sxdg(q); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.x(q); + b.sxdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.x(q); + b.sxdg(q); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.rx(1.0, 0); + comp.rx(0.5, 0); + comp.rx(1.0, 1); + comp.rx(-1.0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.rx(1.0, q0); + b.rx(0.5, q0); + b.rx(1.0, q1); + b.rx(-1.0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.rx(1.0, q0); + b.rx(0.5, q0); + q1 = b.rx(1.0, q1); + b.rx(-1.0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rx(1.5, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rx(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.rx(1.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CRX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.crx(1.0, 0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.crx(1.0, reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.crx(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.crx(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCRX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcrx(1.0, {0, 1}, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, RY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ry(1.0, 0); + comp.ry(0.5, 0); + comp.ry(1.0, 1); + comp.ry(-1.0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.ry(1.0, q0); + b.ry(0.5, q0); + b.ry(1.0, q1); + b.ry(-1.0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.ry(1.0, q0); + b.ry(0.5, q0); + q1 = b.ry(1.0, q1); + b.ry(-1.0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ry(1.5, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ry(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.ry(1.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RZ) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.rz(1.0, 0); + comp.rz(0.5, 0); + comp.rz(1.0, 1); + comp.rz(-1.0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.rz(1.0, q0); + b.rz(0.5, q0); + b.rz(1.0, q1); + b.rz(-1.0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.rz(1.0, q0); + b.rz(0.5, q0); + q1 = b.rz(1.0, q1); + b.rz(-1.0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rz(1.5, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rz(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.rz(1.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, P) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.p(1.0, 0); + comp.p(0.5, 0); + comp.p(1.0, 1); + comp.p(-1.0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.p(1.0, q0); + b.p(0.5, q0); + b.p(1.0, q1); + b.p(-1.0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.p(1.0, q0); + b.p(0.5, q0); + q1 = b.p(1.0, q1); + b.p(-1.0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.p(1.5, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.p(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.p(1.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, R) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.r(1.0, 0.5, 0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.5, reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.5, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.r(1.0, 0.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, RToRX) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RToRY) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CR) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cr(1.0, 0.5, 0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cr(1.0, 0.5, reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cr(1.0, 0.5, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cr(1.0, 0.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCR) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcr(1.0, 0.5, {0, 1}, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.u2(1.0, 0.5, 0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.u2(1.0, 0.5, reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2ToH) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.h(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2ToRX) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2ToRY) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, U) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.u(1.0, 0.5, 0.2, 0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, UToP) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.p(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, UToRX) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, UToRY) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(1.0, reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CU) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cu(1.0, 0.5, 0.2, 0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCU) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcu(1.0, 0.5, 0.2, {0, 1}, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, SWAP) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.swap(0, 1); + comp.swap(0, 1); + comp.swap(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.swap(q0, q1); + b.swap(q0, q1); + b.swap(q0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.swap(q0, q1); + std::tie(q0, q1) = b.swap(q0, q1); + b.swap(q0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.swap(reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.swap(reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.swap(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CSWAP) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.cswap(0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cswap(reg[0], reg[1], reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cswap(reg[0], reg[1], reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.cswap(reg[0], reg[1], reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCSWAP) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcswap({0, 1}, 2, 3); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, iSWAP) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.iswap(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.iswap(reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.iswap(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.iswap(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, DCX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.dcx(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.dcx(reg[0], reg[1]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.dcx(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.dcx(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, ECR) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ecr(0, 1); + comp.ecr(0, 1); + comp.ecr(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.ecr(q0, q1); + b.ecr(q0, q1); + b.ecr(q0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.ecr(q0, q1); + std::tie(q0, q1) = b.ecr(q0, q1); + b.ecr(q0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ecr(reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ecr(reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.ecr(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RXX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rxx(1.0, 0, 1); + comp.rxx(0.5, 0, 1); + comp.rxx(1.0, 1, 2); + comp.rxx(-1.0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.rxx(1.0, q0, q1); + b.rxx(0.5, q0, q1); + b.rxx(1.0, q1, q2); + b.rxx(-1.0, q1, q2); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + std::tie(q0, q1) = b.rxx(1.0, q0, q1); + std::tie(q0, q1) = b.rxx(0.5, q0, q1); + std::tie(q1, q2) = b.rxx(1.0, q1, q2); + b.rxx(-1.0, q1, q2); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rxx(1.5, reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rxx(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.rxx(1.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CRXX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.crxx(1.0, 0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCRXX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcrxx(1.0, {0, 1}, 2, 3); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, RYY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.ryy(1.0, 0, 1); + comp.ryy(0.5, 0, 1); + comp.ryy(1.0, 1, 2); + comp.ryy(-1.0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.ryy(1.0, q0, q1); + b.ryy(0.5, q0, q1); + b.ryy(1.0, q1, q2); + b.ryy(-1.0, q1, q2); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + std::tie(q0, q1) = b.ryy(1.0, q0, q1); + std::tie(q0, q1) = b.ryy(0.5, q0, q1); + std::tie(q1, q2) = b.ryy(1.0, q1, q2); + b.ryy(-1.0, q1, q2); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.ryy(1.5, reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.ryy(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.ryy(1.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RZX) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rzx(1.0, 0, 1); + comp.rzx(0.5, 0, 1); + comp.rzx(1.0, 1, 2); + comp.rzx(-1.0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.rzx(1.0, q0, q1); + b.rzx(0.5, q0, q1); + b.rzx(1.0, q1, q2); + b.rzx(-1.0, q1, q2); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + std::tie(q0, q1) = b.rzx(1.0, q0, q1); + std::tie(q0, q1) = b.rzx(0.5, q0, q1); + std::tie(q1, q2) = b.rzx(1.0, q1, q2); + b.rzx(-1.0, q1, q2); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rzx(1.5, reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rzx(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.rzx(1.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RZZ) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rzz(1.0, 0, 1); + comp.rzz(0.5, 0, 1); + comp.rzz(1.0, 1, 2); + comp.rzz(-1.0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.rzz(1.0, q0, q1); + b.rzz(0.5, q0, q1); + b.rzz(1.0, q1, q2); + b.rzz(-1.0, q1, q2); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + std::tie(q0, q1) = b.rzz(1.0, q0, q1); + std::tie(q0, q1) = b.rzz(0.5, q0, q1); + std::tie(q1, q2) = b.rzz(1.0, q1, q2); + b.rzz(-1.0, q1, q2); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rzz(1.5, reg[0], reg[1]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.rzz(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.rzz(1.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, XXPlusYY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.xx_plus_yy(1.0, 0.5, 0, 1); + comp.xx_plus_yy(0.5, 0.5, 0, 1); + comp.xx_plus_yy(1.0, 1.0, 0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_plus_yy(1.0, 0.5, q0, q1); + b.xx_plus_yy(0.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_plus_yy(1.0, 0.5, q0, q1); + std::tie(q0, q1) = b.xx_plus_yy(0.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CXXPlusYY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.cxx_plus_yy(1.0, 0.5, 0, 1, 2); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCXXPlusYY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcxx_plus_yy(1.0, 0.5, {0, 1}, 2, 3); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, XXMinusYY) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.xx_minus_yy(1.0, 0.5, 0, 1); + comp.xx_minus_yy(0.5, 0.5, 0, 1); + comp.xx_minus_yy(1.0, 1.0, 0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_minus_yy(1.0, 0.5, q0, q1); + b.xx_minus_yy(0.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_minus_yy(1.0, 0.5, q0, q1); + std::tie(q0, q1) = b.xx_minus_yy(0.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, Barrier1) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.barrier(0); + comp.barrier(0); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.barrier(q); + b.barrier(q); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto qubitsOut = b.barrier(reg[0]); + b.barrier(qubitsOut[0]); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.barrier(reg[0]); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.barrier(reg[0]); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = emptyQIR.get(), + }); +} + +TEST_F(CompilerPipelineTest, Barrier2) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.barrier({0, 1}); + comp.barrier({1, 2}); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.barrier({q0, q1}); + b.barrier({q1, q2}); + }); + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto qubitsOut = b.barrier({reg[0], reg[1]}); + b.barrier({qubitsOut[1], reg[2]}); + }); + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.barrier(reg[0]); + b.barrier({reg[1], reg[2]}); + }); + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.barrier(reg[0]); + b.barrier({reg[1], reg[2]}); + }); + + verifyAllStages({ + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), + .qirConversion = emptyQIR.get(), + }); +} + +TEST_F(CompilerPipelineTest, Bell) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.h(0); + comp.cx(0, 1); + + const auto module = importQuantumCircuit(comp); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.h(q0); + b.cx(q0, q1); + }); + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.h(q0); + b.cx(q0, q1); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.h(reg[0]); + b.cx(reg[0], reg[1]); + }); + + verifyAllStages({ + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), + .qirConversion = qir.get(), + }); +} + +} // namespace diff --git a/mlir/unittests/translation/test_translation.cpp b/mlir/unittests/translation/test_translation.cpp index 72cc7fa3c6..7be01a00f6 100644 --- a/mlir/unittests/translation/test_translation.cpp +++ b/mlir/unittests/translation/test_translation.cpp @@ -43,10 +43,10 @@ #include #include -namespace { - using namespace qc; +namespace { + class ImportTest : public ::testing::Test { protected: std::unique_ptr context; @@ -77,11 +77,13 @@ class ImportTest : public ::testing::Test { void TearDown() override {} }; +} // namespace + // ################################################## // # Helper functions // ################################################## -std::string getOutputString(mlir::OwningOpRef* module) { +static std::string getOutputString(mlir::OwningOpRef* module) { std::string outputString; llvm::raw_string_ostream os(outputString); (*module)->print(os); @@ -89,7 +91,7 @@ std::string getOutputString(mlir::OwningOpRef* module) { return outputString; } -std::string formatTargets(std::initializer_list targets) { +static std::string formatTargets(std::initializer_list targets) { std::string s; bool first = true; for (auto t : targets) { @@ -102,7 +104,7 @@ std::string formatTargets(std::initializer_list targets) { return s; } -std::string formatParams(std::initializer_list params) { +static std::string formatParams(std::initializer_list params) { if (params.size() == 0) { return ""; } @@ -122,12 +124,12 @@ std::string formatParams(std::initializer_list params) { return os.str(); } -std::string getCheckStringOperation(const char* op, - std::initializer_list targets) { +static std::string +getCheckStringOperation(const char* op, std::initializer_list targets) { return std::string("CHECK: mqtref.") + op + "() " + formatTargets(targets); } -std::string +static std::string getCheckStringOperationParams(const char* op, std::initializer_list params, std::initializer_list targets) { @@ -137,8 +139,8 @@ getCheckStringOperationParams(const char* op, // Adapted from // https://github.com/llvm/llvm-project/blob/d2b3e86321eaf954451e0a49534fa654dd67421e/llvm/unittests/MIR/MachineMetadata.cpp#L181 -bool checkOutput(const std::string& checkString, - const std::string& outputString) { +static bool checkOutput(const std::string& checkString, + const std::string& outputString) { auto checkBuffer = llvm::MemoryBuffer::getMemBuffer(checkString, ""); auto outputBuffer = llvm::MemoryBuffer::getMemBuffer(outputString, "Output", false); @@ -166,6 +168,8 @@ bool checkOutput(const std::string& checkString, // # Basic tests // ################################################## +namespace { + TEST_F(ImportTest, EntryPoint) { const QuantumComputation qc{}; @@ -291,10 +295,14 @@ TEST_F(ImportTest, Reset0) { ASSERT_TRUE(checkOutput(checkString, outputString)); } +} // namespace + // ################################################## // # Test unitary operations // ################################################## +namespace { + struct TestCaseUnitary { std::string name; // Name of the test case size_t numQubits; @@ -302,11 +310,15 @@ struct TestCaseUnitary { std::string checkStringOperation; }; -std::ostream& operator<<(std::ostream& os, const TestCaseUnitary& testCase) { +} // namespace + +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseUnitary& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { +static std::string +getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { std::string result; // Add entry point @@ -341,6 +353,8 @@ std::string getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { return result; } +namespace { + class OperationTestUnitary : public ImportTest, public ::testing::WithParamInterface {}; @@ -575,21 +589,28 @@ INSTANTIATE_TEST_SUITE_P( .checkStringOperation = "CHECK: mqtref.x() %[[Q0]] nctrl %[[Q1]], %[[Q2]]"})); +} // namespace + // ################################################## // # Test register-controlled if-else operations // ################################################## +namespace { + struct TestCaseIfRegister { std::string name; // Name of the test case ComparisonKind comparisonKind; std::string predicate; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfRegister& testCase) { +} // namespace + +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfRegister& testCase) { return os << testCase.name; } -std::string +static std::string getCheckStringTestCaseIfRegister(const TestCaseIfRegister& testCase) { std::string result; @@ -618,6 +639,8 @@ getCheckStringTestCaseIfRegister(const TestCaseIfRegister& testCase) { return result; } +namespace { + class OperationTestIfRegister : public ImportTest, public ::testing::WithParamInterface {}; @@ -830,10 +853,14 @@ TEST_F(ImportTest, IfElseHandlingFromQasmMultipleStatements) { ASSERT_TRUE(checkOutput(checkString, outputString)); } +} // namespace + // ################################################## // # Test bit-controlled if-else operations // ################################################## +namespace { + struct TestCaseIfBit { std::string name; // Name of the test case ComparisonKind comparisonKind; @@ -841,11 +868,14 @@ struct TestCaseIfBit { bool expectedValueOutput; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfBit& testCase) { +} // namespace + +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfBit& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { +static std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { std::string result; result += R"( @@ -882,6 +912,8 @@ std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { return result; } +namespace { + class OperationTestIfBit : public ImportTest, public ::testing::WithParamInterface { }; @@ -929,11 +961,15 @@ struct TestCaseIfElseBit { std::string elseOperation; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfElseBit& testCase) { +} // namespace + +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfElseBit& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { +static std::string +getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { std::string result; result += R"( @@ -958,6 +994,8 @@ std::string getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { return result; } +namespace { + class OperationTestIfElseBit : public ImportTest, public ::testing::WithParamInterface {}; @@ -1003,10 +1041,14 @@ INSTANTIATE_TEST_SUITE_P( .thenOperation = "x", .elseOperation = "y"})); +} // namespace + // ################################################## // # Test full programs // ################################################## +namespace { + TEST_F(ImportTest, GHZ) { QuantumComputation qc(3, 3); qc.h(0);