# Development Guide Ready to contribute to the Quantum Compilation Suite of the MQSS? This guide will help you get started. ## Development Environment - It is recommended to use a docker container to ensure consistent, stable development environment. - The required `DockerFile` and `devcontainer.json` are provided in `.devcontainer` directory. Build and RUN the docker container using the following commands: ```sh docker build -t mqss-pass-dev -f .devcontainer/Dockerfile . docker run --rm -it \ -v "$PWD":/workspaces/MQSS-Quantum-Compilation-Suite \ -w /workspaces/MQSS-Quantum-Compilation-Suite \ mqss-pass-dev \ bash ``` ## Building the tool - The driver is the `Makefile` within the project root. - The Makefile invokes build scripts within `scripts/`. `build.sh`: Main script for configuring the build for all targets including `mqss-opt` - The ``make` commands to build the targets remain the same as in the [README](../../README.md). Ensure the sequence of commands is followed. ## Project structure (for Current Release) ``` MQSS-Quantum-Compilation-Suite/ ├── cmake/ # Find*.cmake modules: CUDAQ, Catalyst, QDMI, MQT-Core, MQT-QMAP, MQT-QCEC, etc. ├── docs/ # Sphinx documentation source (this page included) ├── include/ │ ├── MQSSCIInterfaces/ │ │ └── MQSSCompiler.h # Simplified C++ library API (see Integrate guide) │ ├── Passes/ │ │ ├── Analysis/ │ │ │ ├── CatalystExtractor.h │ │ │ ├── DialectAnalysisSelector.h │ │ │ ├── Extractor.h │ │ │ └── QuakeExtractor.h │ │ ├── CodeGen/ │ │ │ ├── BasisConversionPatterns.h │ │ │ ├── CMakeLists.txt │ │ │ ├── CodeGenPasses.h │ │ │ ├── CodeGenPasses.td │ │ │ ├── Patterns.h │ │ │ └── StaticAllocas.h │ │ ├── Transforms/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Decomposition.h │ │ │ ├── Dialects.h │ │ │ ├── PassLogic.h │ │ │ ├── Pipelines.h │ │ │ ├── TransformPasses.h │ │ │ └── TransformPasses.td │ │ ├── Verification/ │ │ │ └── Instrumentation.h │ │ └── CMakeLists.txt │ ├── Utils/ │ │ ├── CodegenUtils.h │ │ ├── DebugUtils.h │ │ ├── DialectUtils.h │ │ ├── Error.h │ │ ├── MQTCoreUtils.h │ │ ├── TransformUtils.h │ │ └── TranspilationPassUtils.h │ └── CMakeLists.txt ├── lib/ │ ├── Dialects/ │ │ ├── CMakeLists.txt │ │ └── Dialects.cpp │ ├── MQSSCIInterfaces/ │ │ ├── CMakeLists.txt │ │ └── MQSSCompiler.cpp │ ├── Passes/ │ │ ├── CodeGen/ │ │ │ ├── BasisConversionPass.cpp │ │ │ ├── CMakeLists.txt │ │ │ ├── ConversionPatterns.cpp │ │ │ ├── GlobalizeArrayValuesPass.cpp │ │ │ ├── LLVMDialectToLLVMIRPass.cpp │ │ │ ├── QuakeToQASM2Pass.cpp │ │ │ ├── QuantumToLLVMDialectPass.cpp │ │ │ └── StaticAllocas.cpp │ │ ├── Transforms/ │ │ │ ├── CMakeLists.txt │ │ │ ├── CommonCNOTReversalPass.cpp │ │ │ ├── CommonCommuteAndSwitchPass.cpp │ │ │ ├── CommonDecompositionPass.cpp │ │ │ ├── CommonGateCancellationPass.cpp │ │ │ ├── CommonGateCommutationPass.cpp │ │ │ ├── CommonMappingPass.cpp │ │ │ ├── CommonNormalizeArgAnglePass.cpp │ │ │ ├── CommonPatternReductionPass.cpp │ │ │ └── Pipeline.cpp │ │ ├── Verification/ │ │ │ ├── CMakeLists.txt │ │ │ └── EquivalenceVerification.cpp │ │ └── CMakeLists.txt │ └── CMakeLists.txt ├── scripts/ # build.sh, mqss-cc wrapper, front-end toolchain download scripts ├── tests/ # tests/dialects (lit + FileCheck) and tests/code (end-to-end) ├── CMakeLists.txt └── mqss-cc.cpp ``` - Dialect registration lives in `lib/Dialects`. Dialect-agnostic MLIR optimization passes (namespace `mqss::mqssci::opt`) live in `lib/Passes/Transforms`. Code-generation/lowering passes (namespace `mqss::mqssci::codegen`) live in `lib/Passes/CodeGen`. Circuit-equivalence verification (namespace `mqss::mqssci::verify`) lives in `lib/Passes/Verification` — see [Verification Infrastructure](verification.md). `lib/MQSSCIInterfaces` wraps the pass library behind a single-call `MQSSCompiler` API for consumers who don't need direct `mlir::OpPassManager` access — see [Integrating the MQSS Quantum Compilation Suite](integrate.md). - CUDAQ and Catalyst are included as external dependencies and are downloaded and installed as `cmake` modules. Following targets are built for each of these modules: - CUDAQ: `QuakeDialect CCDialect QECDialect OptimBuilder OptCodeGen` - Catalyst : `MLIRMBQC MLIRQRef MLIRQuantum` These targets incorporate all dialect related headers and API implementations. - MQT-Core, MQT-QMAP, and MQT-QCEC are fetched similarly and are what the verification infrastructure uses to represent circuits and run equivalence checks. ## CMakeLists.txt The pass library is built as one CMake target per directory under `lib/`, each with its own `CMakeLists.txt` declaring exactly the external dependencies (CUDAQ, Catalyst, QDMI, MQT-Core, MQT-QMAP, MQT-QCEC, MLIR conversion libraries, etc.) that its own sources actually need: - `lib/Dialects/CMakeLists.txt` — builds `MQSSSupportedDialects` (dialect registration). - `lib/Passes/CodeGen/CMakeLists.txt` — builds `MQSSCICodeGenPasses` (code-generation/lowering passes), linking `MQSSSupportedDialects` `PUBLIC`. - `lib/Passes/Transforms/CMakeLists.txt` — builds `MQSSCITransformPasses` (dialect-agnostic optimization passes), linking `MQSSCICodeGenPasses` `PUBLIC`. - `lib/Passes/Verification/CMakeLists.txt` — builds `MQSSCIVerificationPasses` (equivalence-checking instrumentation, see [Verification Infrastructure](verification.md)), linking `MQSSSupportedDialects`, `MQT::CoreIR`, and `MQT::QCEC` `PUBLIC`. - `lib/MQSSCIInterfaces/CMakeLists.txt` — builds `MQSSCIInterfaces` (the `MQSSCompiler` library API), linking `MQSSCITransformPasses` and `MQSSCIVerificationPasses` `PUBLIC`. Exposed to external consumers as the `mqss-ci::mqss-ci` alias (see [Integrate](integrate.md)). Note: `MQSSCITransformPasses` and `MQSSCIVerificationPasses` are built as plain static libraries (no `SHARED` keyword). `add_mlir_library` builds a static library in two pieces — an `obj.` object library that does the actual compiling, and a `` static archive that just packages its already- compiled objects — so `target_compile_options`/`target_compile_definitions` calls must target `obj.` (guarded by `if(TARGET obj.)`) to actually affect compilation; targeting `` alone silently does nothing. See [Verification Infrastructure](verification.md#a-cmake-pitfall-specific-to-this-library) for a worked example of this pitfall. Relatedly, these two libraries used to be built `SHARED`; they were switched to static because two `SHARED` libraries that both statically link overlapping LLVM component libraries (as these do, transitively through `MLIRPass`/`MLIRTransforms`) each end up with their own private copy of LLVM's global command-line option registry, which crashes at runtime with an "option registered more than once" error the moment both are loaded into the same process. Because each dependency is declared `PUBLIC` at the target that actually needs it, everything propagates transitively up the chain. `root/CMakeLists.txt` only has to link the final executable against the top of that chain plus MLIR's own driver library: ```cmake target_link_libraries(mqss-opt PUBLIC MLIROptLib MQSSCITransformPasses MQSSCIVerificationPasses) ``` Note: this transitive propagation is convenient, but it isn't a substitute for checking what a given `.cpp` file's callees actually require. If a source file (or a library it calls into, such as `CUDAQ::CodeGen`) needs a specific MLIR conversion library, link it explicitly at the target that contains that source file — don't assume it will arrive transitively from another target further down the chain. ## Testing - We use python-lit along-with ninja and FileCheck to perform dialect-level (input: MLIR dialect; output: MLIR dialect) and optionally end-to-end (input:c++/python code; output: MLIR dialect) testing. In the end, what is tested for correctness is the output optimized/transformed MLIR dialect. In a select few cases, especially the `CodeGen` passes, the backend exchange formats e.g. QIR or OpenQasm2 are tested for correctness. - The tests can be found in directories : `tests/dialects` and `tests/code`. - Dialect-level testing 1. The input dialect is annotated with the RUN command, for e.g.: `// RUN: %mqss-opt %s --CommonCommutePass=mode=CX-X 2>&1 | FileCheck %s` which runs the target/executable `mqss-opt` along-with the pass `CommonCommutePass`. FileCheck looks for strings to match, specified using the `CHECK:` keyword, for e.g. ```sh // CHECK: %out_qubits = quantum.custom "PauliX"() %2 : !quantum.bit // CHECK: %out_qubits_0:2 = quantum.custom "CNOT"() %1, %2 : !quantum.bit, !quantum.bit ``` If an exact match is found in the output dialect, the test succeeds, otherwise the test fails. 1. End-to-End testing (Optional) In this testing, the input is a c++/python code, which is then translated to the input MLIR dialect. The testing then proceeds as in (1). The emphasis here is on testing the front-end translation pipeline as well as the transformed dialects. A wrapper script `mqss-cc` is used to invoke the necessary tools to translate the c++/python code to the input quake or catalyst-quantum dialect. If the input code to MLIR dialect translation fails, then the test itself will fail. An example test file performing such a test is shown below: ```sh // RUN: %mqss-cc %S/../CommuteCNotRxPass.cpp --passes=CommonCommutePass=mode=CX-RX | FileCheck %s // CHECK: quake.rx (%cst_1) %4 : (f64, !quake.ref) -> () // CHECK-NEXT: quake.x [%1] %2 : (!quake.ref, !quake.ref) -> () ``` The test proceeds as follows: 1. mqss-cc parses the command-line arguments `%S/../CommuteCNotRxPass.cpp --passes=CommonCommutePass=mode=CX-RX` 2. Since, a c++ code is the input, it is assumed that the code contains a quantum circuit defined using cudaq (will be updated in the future). 3. The c++ to quake dialect translation pipeline is invoked via the tool `cudaq-quake` 4. Once the input dialect is generated, `mqss-opt` tool is used to apply MQSS optimization/translation passes defined using the `--passes` flag. 5. Finally, the optimized/transformed dialect is emitted which is checked by FileCheck. Note: Make sure that the path to `cudaq-quake` and `mqss-opt` are appended to the `$PATH` environment variable of your shell via the command : `eval "$(make set-target-paths)"`. ## Verification Circuit-equivalence verification is implemented as an `mlir::PassInstrumentation` rather than an ordinary pass — see [Verification Infrastructure](verification.md) for the implementation details, including a CMake pitfall specific to its static library target that's easy to repeat when adding new compile options elsewhere in the project. ## Enabling Pass Debug Information The Pass debug information can be enabled by passing in the flag `--debug` to the `DEBUG_FLAG` variable within the Makefile. Simply remove the flag if no debug information is needed.