Integrating the MQSS Quantum Compilation Suite within your project

MQSSCI (MQSS-Quantum-Compilation-Suite) is a library of MLIR compiler passes for transforming and optimizing quantum programs. This guide walks through adding MQSSCI to your own CMake project, linking against it, and building a pass pipeline — no prior compiler background required.

Using MQSSCI as a CMake Dependency

MQSSCI (MQSS-Quantum-Compilation-Suite) can be integrated into any C++ project as an external CMake module using FetchContent. E.g. within FindMQSSCI.cmake:

set(CUDAQ_AUTO_FETCH ON CACHE BOOL "" FORCE)
set(CATALYST_AUTO_FETCH ON CACHE BOOL "" FORCE)

FetchContent_Declare(mqssci
  GIT_REPOSITORY https://github.com/Munich-Quantum-Software-Stack/MQSS-Quantum-Compilation-Suite.git
  GIT_TAG        <commit-hash or Release-tag>.  # e.g. v2.0.0
)
FetchContent_MakeAvailable(mqssci)

Then, within the appropriate CMakeLists.txt:

find_package(MQSSCI REQUIRED)   # if the file above is named FindMQSSCI.cmake

Note: find_package(MQSSCI REQUIRED) only resolves if CMAKE_MODULE_PATH includes the directory containing FindMQSSCI.cmake, e.g.:

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})

Linking Against the Pass Library

Link your target against the mqss-ci::mqss-ci alias target:

target_link_libraries(<your-target>
  PRIVATE
    mqss-ci::mqss-ci
)

This single target is sufficient — mqss-ci::mqss-ci publicly exports every include directory it needs (its own include/, generated TableGen headers, QDMI, MQT-QMAP, and the MLIR/LLVM headers), so no manual include-path bookkeeping is required in the consuming project.

Including the Headers

Three headers are relevant to consumers, all declared in namespace mqss::opt:

  • Passes/Transforms/Dialects.h — registers the MLIR dialects (Quake, Catalyst’s quantum dialect, and the standard MLIR dialects the passes lower into) that the passes need to understand your program. Include this before parsing or building any MLIR module.

  • Passes/Transforms/Transforms.h — declares every individual pass factory function, for building a custom pass pipeline pass-by-pass.

  • Passes/Transforms/Pipelines.h — declares the three preset optimization levels O1, O2 and O3, for appending a whole preset stage to an mlir::OpPassManager in one call.

Declaring and Using a Pass Pipeline

A pipeline is simply a sequence of passes appended to an mlir::OpPassManager. Which pass factory function you call depends on whether the pass takes options.

Using a Preset Pipeline Directly in Your Project

Call one of the preset optimization pipelines — O1, O2, or O3 — directly:

#include "Passes/Transforms/Dialects.h"
#include "Passes/Transforms/Pipelines.h"

mlir::DialectRegistry registry;

// 1. Register all dialects and declare a MLIR context
mqss::opt::registerMQSSDialects(registry);
mlir::MLIRContext context(registry);
context.loadAllAvailableDialects();

//  2. Parse the source dialect and create MLIR module
auto module = mlir::parseSourceFile<mlir::ModuleOp>(src_path, &context);
if (!module) {
  llvm::errs() << "failed to parse MLIR file\n";
}

// 3. Declare the MLIR Pass Manager
mlir::PassManager pm(&context);

// 4. Add passes from the O1 pass pipeline
mqss::opt::O1(pm);

// Run the passes
if (mlir::failed(pm.run(*module))) {
  llvm::errs() << "Compiler: Pipeline failed\n";
}

// 5. Add a CodeGen Pass and RUN the pass
pm.addPass(mqss::opt::QuakeToQASM2Pass());
if (mlir::failed(pm.run(*module))) {
  llvm::errs() << "Compiler: Conversion of Quake to QASM2 failed\n";
}

registerMQSSDialects fills a DialectRegistry with everything MQSSCI’s passes need to understand your program (Quake, Catalyst’s quantum dialect, and the standard MLIR dialects the passes lower into). Construct the MLIRContext once, locally — it can’t be copied or moved — and keep it alive for as long as you’re parsing MLIR or running passes.

Please refer to Passes/Transforms/Pipelines.h for details on which specific passes the pipelines include.

Declaring a custom Pass Pipeline

If the presets don’t fit your use case, assemble your own pipeline by adding individual passes to an mlir::OpPassManager one at a time, in whatever order you need. Every pass factory lives in Passes/Transforms/Transforms.h, and falls into one of two shapes depending on whether the pass takes options.

Passes Without Options

Passes with no configurable options are constructed by calling their factory function directly:

#include "Passes/Transforms/Transforms.h"

mlir::OpPassManager pm;
pm.addPass(mqss::opt::CommonCNOTReversePass());
pm.addPass(mqss::opt::CommonNormalizeArgAnglePass());

Check Transforms.h for other option-free passes.

Passes With Options

Passes that expose options have a companion <PassName>Options struct. Populate it and pass it to the corresponding create<PassName> factory. For example:

#include "Passes/Transforms/Transforms.h"

mlir::OpPassManager pm;

CommonGateCancellationPassOptions cancelOpts;
cancelOpts.mode = "CancelGate";
pm.addPass(mqss::opt::createCommonGateCancellationPass(cancelOpts));

CommonCommutePassOptions commuteOpts;
commuteOpts.mode = "CX-RX";
pm.addPass(mqss::opt::createCommonCommutePass(commuteOpts));

Other option-bearing passes: CommonSwitchPass, CommonReductionPass, CommonDecompositionPass, CommonMappingPass, BasisConversionPass. See Passes for the full list of valid option values for each.

Example: The O2 Pipeline

The built-in O2 optimization level combines both styles above into a single reusable pipeline (lib/Passes/Transforms/pipeline.cpp):

void mqss::opt::O2(mlir::OpPassManager &pm) {
  CommonGateCancellationPassOptions CancelOpts;
  CommonCommutePassOptions CommuteOpts;
  CancelOpts.mode = "CancelGate";
  CommuteOpts.mode = "CX-RX";

  pm.addPass(createCommonGateCancellationPass(CancelOpts));
  pm.addPass(CommonCNOTReversePass());
  pm.addPass(createCommonCommutePass(CommuteOpts));

  // Standard MLIR passes
  pm.addPass(createCSEPass());
  pm.addPass(createCanonicalizerPass());
}