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:

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. 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. 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.

  • 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), 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).

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.<name> object library that does the actual compiling, and a <name> static archive that just packages its already- compiled objects — so target_compile_options/target_compile_definitions calls must target obj.<name> (guarded by if(TARGET obj.<name>)) to actually affect compilation; targeting <name> alone silently does nothing. See Verification Infrastructure 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:

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.

    // 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:

       // 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 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.