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
DockerFileanddevcontainer.jsonare provided in.devcontainerdirectory. 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
Makefilewithin the project root.The Makefile invokes build scripts within
scripts/.build.sh: Main script for configuring the build for all targets includingmqss-optThe ``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 (namespacemqss::mqssci::opt) live inlib/Passes/Transforms. Code-generation/lowering passes (namespacemqss::mqssci::codegen) live inlib/Passes/CodeGen. Circuit-equivalence verification (namespacemqss::mqssci::verify) lives inlib/Passes/Verification— see Verification Infrastructure.lib/MQSSCIInterfaceswraps the pass library behind a single-callMQSSCompilerAPI for consumers who don’t need directmlir::OpPassManageraccess — see Integrating the MQSS Quantum Compilation Suite.CUDAQ and Catalyst are included as external dependencies and are downloaded and installed as
cmakemodules. Following targets are built for each of these modules:CUDAQ:
QuakeDialect CCDialect QECDialect OptimBuilder OptCodeGenCatalyst :
MLIRMBQC MLIRQRef MLIRQuantumThese 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— buildsMQSSSupportedDialects(dialect registration).lib/Passes/CodeGen/CMakeLists.txt— buildsMQSSCICodeGenPasses(code-generation/lowering passes), linkingMQSSSupportedDialectsPUBLIC.lib/Passes/Transforms/CMakeLists.txt— buildsMQSSCITransformPasses(dialect-agnostic optimization passes), linkingMQSSCICodeGenPassesPUBLIC.lib/Passes/Verification/CMakeLists.txt— buildsMQSSCIVerificationPasses(equivalence-checking instrumentation, see Verification Infrastructure), linkingMQSSSupportedDialects,MQT::CoreIR, andMQT::QCECPUBLIC.lib/MQSSCIInterfaces/CMakeLists.txt— buildsMQSSCIInterfaces(theMQSSCompilerlibrary API), linkingMQSSCITransformPassesandMQSSCIVerificationPassesPUBLIC. Exposed to external consumers as themqss-ci::mqss-cialias (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
CodeGenpasses, the backend exchange formats e.g. QIR or OpenQasm2 are tested for correctness.The tests can be found in directories :
tests/dialectsandtests/code.Dialect-level testing
The input dialect is annotated with the RUN command, for e.g.:
// RUN: %mqss-opt %s --CommonCommutePass=mode=CX-X 2>&1 | FileCheck %swhich runs the target/executablemqss-optalong-with the passCommonCommutePass. FileCheck looks for strings to match, specified using theCHECK: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.
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-ccis 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:
mqss-cc parses the command-line arguments
%S/../CommuteCNotRxPass.cpp --passes=CommonCommutePass=mode=CX-RXSince, 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).
The c++ to quake dialect translation pipeline is invoked via the tool
cudaq-quakeOnce the input dialect is generated,
mqss-opttool is used to apply MQSS optimization/translation passes defined using the--passesflag.Finally, the optimized/transformed dialect is emitted which is checked by FileCheck.
Note: Make sure that the path to
cudaq-quakeandmqss-optare appended to the$PATHenvironment 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.