Writing Passes for the MQSS¶
Following text provides guides you through the process of adding an MLIR pass and adding an MLIR dialect to the MQSS Quantum Compilation Suite
Adding your pass¶
Two types of passes can be added : MLIR Dialect-Agnostic and Dialect-specific
Both type of Passes can be added within
lib/Passes.A Dialect Agnostic Pass should make use of our abstractions mentioned in
include/Passes/Analysis. The key abstraction here is the interface classMyModuleAnalysis. For every supported dialect, an implementation of this interface should be provided. Currently, We have two such implementations :QuakeAnalysisandCatalystQuantumAnalysisfor Quake and Catalyst-quantum dialects.There is a
DialectAnalysisSelectorclass withinDialectAnalysisSelector.hwhich selects the appropriateMyModuleAnalysisobject based on the detected dialect of the input MLIR module.A key step in adding your pass is pass registration. This ensures that the passes can be found by the target
mqss-opt.To register a pass we need to make additions to 2 files
Transforms.handTransforms.td. These files can be found withininclude/Passes/Transforms.Here is an example of pass registration within
Transforms.td:
def CommonNormalizeArgAnglePass : Pass<"CommonNormalizeArgAnglePass", "mlir::ModuleOp"> { let summary = "Normalize Arg angle of RX, RY, RZ gates. "; let description = [{ Normalize Arg angle of RX, RY, RZ gates. }]; let constructor = "mqss::opt::CommonNormalizeArgAnglePass()"; }
The pass is registered for the
mqss-opttarget. The pass does not have any options.If your pass will need options/arguments to be passed, you can add the field:
let options = [ Option<"<optional-name>", "mode", "<option-type>", /*default=*/"\"<default value>\"", "Description of Options.">, ];
In
Transforms.hwithin themqss::optnamespace, the following should be added:std::unique_ptr<mlir::Pass> CommonNormalizeArgAnglePass();
The above line is based on the definition, signature and structure of the pass viz. discussed in the next section.
Pass Structure¶
A basic pass structure is as shown:
#include "Passes/Transforms/PassUtils.h"
using namespace mlir;
using namespace llvm;
namespace mqss::opt {
#define GEN_PASS_DEF_COMMONCNOTREVERSEPASS
#include "Passes/Transforms/Transforms.h.inc"
} // namespace mqss::opt
namespace {
class CommonCNOTReverse : public mqss_backend::impl::CommonCNOTReversePassBase<CommonCNOTReverse> {
public:
// Default constructor (required for pass registry)
CommonCNOTReverse() = default;
void runOnOperation() override {
...;
}
};
} // namespace
std::unique_ptr<mlir::Pass> mqss_backend::CommonCNOTReversePass() {
return std::make_unique<CommonCNOTReverse>();
}
After the pass is registered within Transforms.td, and the project is re-built, a templated class
is automatically created within build/include/Passes/Transforms.h.inc. The class looks like this:
#ifdef GEN_PASS_DECL_COMMONCNOTREVERSEPASS
#undef GEN_PASS_DECL_COMMONCNOTREVERSEPASS
#endif // GEN_PASS_DECL_COMMONCNOTREVERSEPASS
#ifdef GEN_PASS_DEF_COMMONCNOTREVERSEPASS
namespace impl {
template <typename DerivedT>
class CommonCNOTReversePassBase : public ::mlir::OperationPass<mlir::ModuleOp> {
public:
using Base = CommonCNOTReversePassBase;
CommonCNOTReversePassBase() : ::mlir::OperationPass<mlir::ModuleOp>(::mlir::TypeID::get<DerivedT>()) {}
CommonCNOTReversePassBase(const CommonCNOTReversePassBase &other) : ::mlir::OperationPass<mlir::ModuleOp>(other) {}
CommonCNOTReversePassBase& operator=(const CommonCNOTReversePassBase &) = delete;
CommonCNOTReversePassBase(CommonCNOTReversePassBase &&) = delete;
CommonCNOTReversePassBase& operator=(CommonCNOTReversePassBase &&) = delete;
~CommonCNOTReversePassBase() = default;
/// Returns the command-line argument attached to this pass.
static constexpr ::llvm::StringLiteral getArgumentName() {
return ::llvm::StringLiteral("CommonCNOTReversePass");
}
::llvm::StringRef getArgument() const override { return "CommonCNOTReversePass"; }
::llvm::StringRef getDescription() const override { return R"PD(Reverse the control and targets of each CNot gate in a circuit)PD"; }
/// Returns the derived pass name.
static constexpr ::llvm::StringLiteral getPassName() {
return ::llvm::StringLiteral("CommonCNOTReversePass");
}
::llvm::StringRef getName() const override { return "CommonCNOTReversePass"; }
/// Support isa/dyn_cast functionality for the derived pass class.
static bool classof(const ::mlir::Pass *pass) {
return pass->getTypeID() == ::mlir::TypeID::get<DerivedT>();
}
/// A clone method to create a copy of this pass.
std::unique_ptr<::mlir::Pass> clonePass() const override {
return std::make_unique<DerivedT>(*static_cast<const DerivedT *>(this));
}
/// Return the dialect that must be loaded in the context before this pass.
void getDependentDialects(::mlir::DialectRegistry ®istry) const override {
}
/// Explicitly declare the TypeID for this class. We declare an explicit private
/// instantiation because Pass classes should only be visible by the current
/// library.
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CommonCNOTReversePassBase<DerivedT>)
protected:
private:
};
} // namespace impl
#undef GEN_PASS_DEF_COMMONCNOTREVERSEPASS
#endif // GEN_PASS_DEF_COMMONCNOTREVERSEPASS
The pass class CommonCNOTReverse inherits from the pass base class within:
build/_deps/LLVM-22.1.0-toolchain/include/mlir/Pass/Pass.h.
Adding a Dialect¶
Adding a dialect requires definitions for its Operations, Types, Attributes, Interfaces, and related
constructs. These definitions are emitted by TableGen into .h.inc (declarations) and .cpp.inc
(definitions) files, which are generated automatically as part of a successful build. Our
compiler depends on the generated headers and sources for the Quake and Catalyst-quantum dialects.
To obtain them, we selectively build cudaq-quantum (referred to as CUDAQ) and Catalyst from source,
wiring them in as external CMake dependencies:
Quake dialect — provided by CUDAQ. We build its
QuakeDialecttarget. See cmake/FindCUDAQ.cmake.Catalyst-quantum dialect — provided by Catalyst. The equivalent target is
MLIRQuantum. See cmake/FindCatalyst.cmake.
For details on how each dialect’s definitions are declared, refer to the upstream source or within
build/_deps under the respective framework’s include/ directory. To add a dialect to our compiler
and enable our quantum abstractions on top of it, follow these steps:
An implementation of the
MyModuleAnalysisclass for the dialect needs to be added withininclude/Passes/Analysis. The name of this file should follow the convention : “{Dialect-Name}”Extractor.h.Include the header files with the required dialect definitions within
include/Utils/dialectutils.h.Add an entry for the dialect within
include/Analysis/DialectAnalysisSelector.h.Register the dialect within
mqss-cc.cpp(registry.insert<>()).