External compiler pass plugins¶
cudaq-opt can load a custom MLIR operation pass from a shared library. This
keeps the pass outside CUDA-Q’s built-in pass catalog and production pipelines
while allowing it to transform CUDA-Q IR with the same MLIR APIs used by
built-in passes.
CUDA-Q currently builds and tests pass plugins within a CUDA-Q development
build. A plugin must use CUDA-Q, LLVM, and MLIR headers and libraries compatible
with the cudaq-opt binary that loads it. Rebuild the plugin when those
dependencies change.
Implement and register the pass¶
A plugin pass is an MLIR operation pass with a textual argument. Include
cudaq/Support/Plugin.h and place CUDAQ_REGISTER_MLIR_PASS at file scope
after the pass definition. The macro exports the CUDA-Q plugin entry point and
registers one default-constructible pass.
The complete example below is included from the source compiled by the plugin
test target. It replaces each quake.h operation with
quake.s so that the regression can observe the transformation. This example
demonstrates plugin registration and does not preserve circuit semantics.
/*******************************************************************************
* Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/
#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h"
#include "cudaq/Optimizer/Transforms/CommutationAwareRewrite.h"
#include "cudaq/Support/Plugin.h"
// Here is an example MLIR Pass that one can write externally and
// use via the cudaq-opt tool, with the --load-cudaq-plugin flag.
// The pass here is simple, replace Hadamard operations with S operations.
using namespace mlir;
namespace {
struct ReplaceH : public OpRewritePattern<cudaq::quake::HOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(cudaq::quake::HOp hOp,
PatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<cudaq::quake::SOp>(
hOp, hOp.isAdj(), hOp.getParameters(), hOp.getControls(),
hOp.getTargets());
return success();
}
};
class CustomPassPlugin
: public PassWrapper<CustomPassPlugin, OperationPass<func::FuncOp>> {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CustomPassPlugin)
llvm::StringRef getArgument() const override { return "cudaq-custom-pass"; }
void runOnOperation() override {
auto circuit = getOperation();
auto ctx = circuit.getContext();
cudaq::opt::CommutationAwareRewriteDriver driver(*ctx);
driver.get_patterns().add<ReplaceH>(ctx);
if (failed(driver.run(circuit.getBody()))) {
circuit.emitOpError("simple pass failed");
signalPassFailure();
}
}
};
} // namespace
CUDAQ_REGISTER_MLIR_PASS(CustomPassPlugin)
Build the plugin¶
The tested CMake target uses LLVM’s pass-plugin helper and depends on the generated Quake dialect headers:
# ============================================================================ #
# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #
include(HandleLLVMOptions)
add_llvm_pass_plugin(CustomPassPlugin CustomPassPlugin.cpp)
# Depends on QuakeDialect TableGen to use the generated `.h.inc` files.
add_dependencies(CustomPassPlugin QuakeDialect)
# Link no CUDA-Q or MLIR library: cudaq-opt links MLIR/LLVM statically and
# re-exports it for plugins, so every symbol resolves from the host. Linking
# libcudaqMLIR would pull a second copy of LLVM's globals into the process and
# abort on duplicate cl::opt registration.
Build that target from a configured CUDA-Q build tree:
cmake --build build --target CustomPassPlugin
Load and test the plugin¶
Load the shared library before naming its registered pass. This Linux command uses the paths produced by the in-tree build:
build/bin/cudaq-opt input.qke \
--load-cudaq-plugin build/lib/CustomPassPlugin.so \
--cudaq-custom-pass
A corresponding regression test loads the plugin into cudaq-opt and checks
the transformed IR. The pass is registered only for that invocation and is not
added to a CUDA-Q compilation pipeline.