-
-
Notifications
You must be signed in to change notification settings - Fork 72
β¨ Add an unroll-modifiers pass for unrolling multi-operation modifiers
#2015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
denialhaag
wants to merge
13
commits into
main
Choose a base branch
from
unroll-modifiers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+936
β114
Open
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
eee1742
β¨ Add an `unroll-modifiers` pass for multi-operation modifiers
denialhaag 0cf9047
Address the Rabbit's comments
denialhaag 7707cba
Reject StaticOps in modifier bodies
denialhaag 61cb3d0
Clean up a bit
denialhaag 9ff2e9d
Log modifiers that cannot be unrolled
denialhaag 050511d
Fix linter errors
denialhaag 491e802
Share the unrolled ctrl and inv programs
denialhaag a52566b
Fix typo
denialhaag 9cb5201
Update changelog
denialhaag c505ade
Merge branch 'main' into unroll-modifiers
denialhaag d043d9b
β
Reject qubit captures in QCO modifiers
burgholzer 65d12fc
π¨ Follow fixed-width integer style
burgholzer 7170cea
π Document QCO modifier capture rules
burgholzer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,261 @@ | ||
| /* | ||
| * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM | ||
| * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH | ||
| * All rights reserved. | ||
| * | ||
| * SPDX-License-Identifier: MIT | ||
| * | ||
| * Licensed under the MIT License | ||
| */ | ||
|
|
||
| #include "mlir/Dialect/QC/IR/QCOps.h" | ||
| #include "mlir/Dialect/QCO/IR/QCOOps.h" | ||
| #include "mlir/Dialect/Utils/Transforms/Passes.h" | ||
| #include "mlir/Dialect/Utils/Utils.h" | ||
|
|
||
| #include <llvm/ADT/STLExtras.h> | ||
| #include <llvm/ADT/SmallVector.h> | ||
| #include <llvm/ADT/SmallVectorExtras.h> | ||
| #include <llvm/ADT/TypeSwitch.h> | ||
| #include <mlir/IR/Block.h> | ||
| #include <mlir/IR/IRMapping.h> | ||
| #include <mlir/IR/OpDefinition.h> | ||
| #include <mlir/IR/Operation.h> | ||
| #include <mlir/IR/PatternMatch.h> | ||
| #include <mlir/IR/Value.h> | ||
| #include <mlir/Interfaces/SideEffectInterfaces.h> | ||
| #include <mlir/Support/LLVM.h> | ||
| #include <mlir/Support/LogicalResult.h> | ||
|
|
||
| namespace mlir::mqt { | ||
|
|
||
| #define GEN_PASS_DEF_UNROLLMODIFIERS | ||
| #include "mlir/Dialect/Utils/Transforms/Passes.h.inc" | ||
|
|
||
| namespace { | ||
|
|
||
| /// Return the unitary operations in @p body. | ||
| template <typename UnitaryOpInterface> | ||
| SmallVector<UnitaryOpInterface> getBodyUnitaries(Block& body) { | ||
| return llvm::to_vector(body.getOps<UnitaryOpInterface>()); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// Return the distinct qubit operands of @p op in operand order. | ||
| template <typename QubitType> | ||
| SmallVector<Value> getQubitOperands(Operation* op) { | ||
| SmallVector<Value> qubits; | ||
| for (auto operand : op->getOperands()) { | ||
| if (isa<QubitType>(operand.getType()) && | ||
| !llvm::is_contained(qubits, operand)) { | ||
| qubits.push_back(operand); | ||
| } | ||
| } | ||
| return qubits; | ||
| } | ||
|
|
||
| /// Move the classical operations of @p body in front of @p modifier. | ||
| /// | ||
| /// Fails if a classical operation is impure or depends on values defined in | ||
| /// @p body. | ||
| template <typename UnitaryOpInterface> | ||
| LogicalResult hoistClassicalOps(Block& body, Operation* modifier, | ||
| RewriterBase& rewriter) { | ||
| const auto isClassical = [](Operation& op) { | ||
| return !isa<UnitaryOpInterface>(op) && | ||
| !op.hasTrait<OpTrait::IsTerminator>(); | ||
| }; | ||
| for (auto& op : body) { | ||
| if (isClassical(op) && | ||
| (!isPure(&op) || llvm::any_of(op.getOperands(), [&](Value operand) { | ||
| return operand.getParentBlock() == &body; | ||
| }))) { | ||
| return failure(); | ||
| } | ||
| } | ||
| for (auto& op : llvm::make_early_inc_range(body)) { | ||
| if (isClassical(op)) { | ||
| rewriter.moveOpBefore(&op, modifier); | ||
| } | ||
| } | ||
| return success(); | ||
| } | ||
|
|
||
| /// Clone @p unitary into the body of a new modifier, replacing its qubit | ||
| /// operands @p qubits with the block arguments @p args, and return its results. | ||
| SmallVector<Value> cloneIntoBody(Operation* unitary, ValueRange qubits, | ||
| ValueRange args, RewriterBase& rewriter) { | ||
| IRMapping mapping; | ||
| mapping.map(qubits, args); | ||
| auto results = rewriter.clone(*unitary, mapping)->getResults(); | ||
| return {results.begin(), results.end()}; | ||
| } | ||
|
|
||
| //===----------------------------------------------------------------------===// | ||
| // QC | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// Unroll a `qc.ctrl` modifier with more than one body unitary. | ||
| LogicalResult unrollModifier(qc::CtrlOp op, RewriterBase& rewriter) { | ||
| if (op.getNumBodyUnitaries() < 2) { | ||
| return failure(); | ||
| } | ||
| auto* body = op.getBody(); | ||
| if (failed(hoistClassicalOps<qc::UnitaryOpInterface>(*body, op, rewriter))) { | ||
| return failure(); | ||
| } | ||
|
|
||
| rewriter.setInsertionPoint(op); | ||
| for (auto unitary : getBodyUnitaries<qc::UnitaryOpInterface>(*body)) { | ||
| const auto qubits = getQubitOperands<qc::QubitType>(unitary); | ||
| const auto targets = llvm::map_to_vector(qubits, [&](Value qubit) { | ||
| return utils::getValueFromBlockArgument(qubit, op.getTargets()); | ||
| }); | ||
| qc::CtrlOp::create(rewriter, op.getLoc(), op.getControls(), targets, | ||
| [&](ValueRange args) { | ||
| cloneIntoBody(unitary, qubits, args, rewriter); | ||
| }); | ||
| } | ||
| rewriter.eraseOp(op); | ||
| return success(); | ||
| } | ||
|
|
||
| /// Unroll a `qc.inv` modifier with more than one body unitary. | ||
| LogicalResult unrollModifier(qc::InvOp op, RewriterBase& rewriter) { | ||
| if (op.getNumBodyUnitaries() < 2) { | ||
| return failure(); | ||
| } | ||
| auto* body = op.getBody(); | ||
| if (failed(hoistClassicalOps<qc::UnitaryOpInterface>(*body, op, rewriter))) { | ||
| return failure(); | ||
| } | ||
|
|
||
| rewriter.setInsertionPoint(op); | ||
| // (a b)^-1 = b^-1 a^-1, so the operations are inverted in reverse order. | ||
| auto unitaries = getBodyUnitaries<qc::UnitaryOpInterface>(*body); | ||
| for (auto unitary : llvm::reverse(unitaries)) { | ||
| const auto qubits = getQubitOperands<qc::QubitType>(unitary); | ||
| const auto targets = llvm::map_to_vector(qubits, [&](Value qubit) { | ||
| return utils::getValueFromBlockArgument(qubit, op.getQubits()); | ||
| }); | ||
| qc::InvOp::create(rewriter, op.getLoc(), targets, [&](ValueRange args) { | ||
| cloneIntoBody(unitary, qubits, args, rewriter); | ||
| }); | ||
| } | ||
| rewriter.eraseOp(op); | ||
| return success(); | ||
| } | ||
|
|
||
| //===----------------------------------------------------------------------===// | ||
| // QCO | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// Check that every unitary operation in @p body threads its qubit operands to | ||
| /// its results, which is required to rewire the unrolled modifiers. | ||
| bool hasThreadedBodyUnitaries(Block& body) { | ||
| return llvm::all_of(body.getOps<qco::UnitaryOpInterface>(), | ||
| [](qco::UnitaryOpInterface unitary) { | ||
| return unitary->getNumResults() == | ||
| getQubitOperands<qco::QubitType>(unitary).size(); | ||
| }); | ||
| } | ||
|
|
||
| /// Unroll a `qco.ctrl` modifier with more than one body unitary. | ||
| LogicalResult unrollModifier(qco::CtrlOp op, RewriterBase& rewriter) { | ||
| auto* body = op.getBody(); | ||
| if (op.getNumBodyUnitaries() < 2 || !hasThreadedBodyUnitaries(*body)) { | ||
| return failure(); | ||
| } | ||
| if (failed(hoistClassicalOps<qco::UnitaryOpInterface>(*body, op, rewriter))) { | ||
| return failure(); | ||
| } | ||
|
|
||
| rewriter.setInsertionPoint(op); | ||
| // Maps the qubits of the body to the qubits threaded through the new | ||
| // modifiers. | ||
| IRMapping qubits; | ||
| qubits.map(body->getArguments(), op.getTargetsIn()); | ||
|
|
||
| SmallVector<Value> controls(op.getControlsIn()); | ||
| for (auto unitary : getBodyUnitaries<qco::UnitaryOpInterface>(*body)) { | ||
| const auto operands = getQubitOperands<qco::QubitType>(unitary); | ||
| const auto targets = llvm::map_to_vector( | ||
| operands, [&](Value qubit) { return qubits.lookup(qubit); }); | ||
| auto ctrlOp = qco::CtrlOp::create( | ||
| rewriter, op.getLoc(), controls, targets, | ||
| [&](ValueRange args) -> SmallVector<Value> { | ||
| return cloneIntoBody(unitary, operands, args, rewriter); | ||
| }); | ||
| auto controlsOut = ctrlOp.getControlsOut(); | ||
| controls.assign(controlsOut.begin(), controlsOut.end()); | ||
| qubits.map(unitary->getResults(), ctrlOp.getTargetsOut()); | ||
| } | ||
|
|
||
| SmallVector<Value> results(controls); | ||
| for (auto yielded : body->getTerminator()->getOperands()) { | ||
| results.push_back(qubits.lookup(yielded)); | ||
| } | ||
| rewriter.replaceOp(op, results); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| return success(); | ||
| } | ||
|
|
||
| /// Unroll a `qco.inv` modifier with more than one body unitary. | ||
| LogicalResult unrollModifier(qco::InvOp op, RewriterBase& rewriter) { | ||
| auto* body = op.getBody(); | ||
| if (op.getNumBodyUnitaries() < 2 || !hasThreadedBodyUnitaries(*body)) { | ||
| return failure(); | ||
| } | ||
| if (failed(hoistClassicalOps<qco::UnitaryOpInterface>(*body, op, rewriter))) { | ||
| return failure(); | ||
| } | ||
|
|
||
| rewriter.setInsertionPoint(op); | ||
| // (a b)^-1 = b^-1 a^-1, so the operations are inverted in reverse order. | ||
| // Consequently, the inputs of the modifier feed the qubits that its body | ||
| // yields. | ||
| IRMapping qubits; | ||
| qubits.map(body->getTerminator()->getOperands(), op.getQubitsIn()); | ||
|
|
||
| auto unitaries = getBodyUnitaries<qco::UnitaryOpInterface>(*body); | ||
| for (auto unitary : llvm::reverse(unitaries)) { | ||
| const auto operands = getQubitOperands<qco::QubitType>(unitary); | ||
| const auto targets = | ||
| llvm::map_to_vector(unitary->getResults(), | ||
| [&](Value qubit) { return qubits.lookup(qubit); }); | ||
| auto invOp = qco::InvOp::create(rewriter, op.getLoc(), targets, | ||
| [&](ValueRange args) -> SmallVector<Value> { | ||
| return cloneIntoBody(unitary, operands, | ||
| args, rewriter); | ||
| }); | ||
| qubits.map(operands, invOp.getResults()); | ||
| } | ||
|
|
||
| rewriter.replaceOp( | ||
| op, llvm::map_to_vector(body->getArguments(), | ||
| [&](Value arg) { return qubits.lookup(arg); })); | ||
| return success(); | ||
| } | ||
|
|
||
| struct UnrollModifiers final : impl::UnrollModifiersBase<UnrollModifiers> { | ||
| protected: | ||
| void runOnOperation() override { | ||
| SmallVector<Operation*> modifiers; | ||
| getOperation()->walk([&](Operation* op) { | ||
| if (isa<qc::CtrlOp, qc::InvOp, qco::CtrlOp, qco::InvOp>(op)) { | ||
| modifiers.push_back(op); | ||
| } | ||
| }); | ||
|
|
||
| // The walk visits nested modifiers before their parents, so unrolling the | ||
| // collected modifiers in order reaches a fixpoint in a single sweep. | ||
| IRRewriter rewriter(&getContext()); | ||
| for (auto* modifier : modifiers) { | ||
| llvm::TypeSwitch<Operation*>(modifier) | ||
| .Case<qc::CtrlOp, qc::InvOp, qco::CtrlOp, qco::InvOp>([&](auto op) { | ||
| static_cast<void>(unrollModifier(op, rewriter)); | ||
| }); | ||
| } | ||
|
denialhaag marked this conversation as resolved.
|
||
| } | ||
| }; | ||
|
|
||
| } // namespace | ||
| } // namespace mlir::mqt | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.