diff --git a/CHANGELOG.md b/CHANGELOG.md index 706103e11b..3b180bc22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ Add conversions between Jeff and QCO ([#1479], [#1548], [#1565]) ([**@denialhaag**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588]) ([**@MatthiasReumann**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1570], [#1572], [#1573]) + ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1569], [#1570], [#1572], [#1573]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) ### Changed @@ -344,6 +344,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1572]: https://github.com/munich-quantum-toolkit/core/pull/1572 [#1571]: https://github.com/munich-quantum-toolkit/core/pull/1571 [#1570]: https://github.com/munich-quantum-toolkit/core/pull/1570 +[#1569]: https://github.com/munich-quantum-toolkit/core/pull/1569 [#1568]: https://github.com/munich-quantum-toolkit/core/pull/1568 [#1565]: https://github.com/munich-quantum-toolkit/core/pull/1565 [#1564]: https://github.com/munich-quantum-toolkit/core/pull/1564 diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index fdf5ab7310..f602903950 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -110,7 +110,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Get a static qubit by index - * @param index The qubit index (must be non-negative) + * @param index The qubit index * @return A qubit reference * * @par Example: @@ -121,7 +121,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * %q0 = qc.static 0 : !qc.qubit * ``` */ - Value staticQubit(int64_t index); + Value staticQubit(uint64_t index); /** * @brief Allocate a qubit register diff --git a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h index 137c052b77..69d11e8471 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -52,6 +52,7 @@ template class TargetAndParameterArityTrait { static size_t getNumQubits() { return T; } static size_t getNumTargets() { return T; } static size_t getNumControls() { return 0; } + static ValueRange getControls() { return {}; } Value getQubit(size_t i) { if constexpr (T == 0) { @@ -71,6 +72,9 @@ template class TargetAndParameterArityTrait { } return this->getOperation()->getOperand(i); } + ValueRange getTargets() { + return this->getOperation()->getOperands().slice(0, T); + } static Value getControl([[maybe_unused]] size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); @@ -84,6 +88,10 @@ template class TargetAndParameterArityTrait { } return this->getOperation()->getOperand(T + i); } + + ValueRange getParameters() { + return this->getOperation()->getOperands().slice(T, P); + } }; }; diff --git a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td index 6f77839a65..9b2399bdff 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -44,12 +44,18 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { (ins "size_t":$i)>, InterfaceMethod<"Returns the i-th control qubit.", "Value", "getControl", (ins "size_t":$i)>, + InterfaceMethod<"Returns a range of all target qubits.", "ValueRange", + "getTargets", (ins)>, + InterfaceMethod<"Returns a range of all control qubits.", "ValueRange", + "getControls", (ins)>, // Parameter handling InterfaceMethod<"Returns the number of parameters.", "size_t", "getNumParams", (ins)>, InterfaceMethod<"Returns the i-th parameter.", "Value", "getParameter", (ins "size_t":$i)>, + InterfaceMethod<"Returns a range of all parameters.", "ValueRange", + "getParameters", (ins)>, // Convenience methods InterfaceMethod<"Returns true if the operation has any control qubits, " diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 9ca5480d2f..e42965e721 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -110,6 +110,10 @@ def StaticOp : QCOp<"static", [Pure]> { let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; + + let builders = [OpBuilder<(ins "uint64_t":$index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), index); + }]>]; } //===----------------------------------------------------------------------===// @@ -910,11 +914,14 @@ def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { size_t getNumQubits() { return getNumTargets(); } size_t getNumTargets() { return getQubits().size(); } static size_t getNumControls() { return 0; } + static ValueRange getControls() { return {}; } Value getQubit(size_t i) { return getTarget(i); } Value getTarget(size_t i); + ValueRange getTargets() { return getQubits(); } static Value getControl(size_t i) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } static size_t getNumParams() { return 0; } static Value getParameter(size_t i) { llvm::reportFatalUsageError("BarrierOp does not have parameters"); } + static ValueRange getParameters() { return {}; } static StringRef getBaseSymbol() { return "barrier"; } }]; } @@ -969,9 +976,11 @@ def CtrlOp size_t getNumControls() { return getControls().size(); } Value getQubit(size_t i); Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } + ValueRange getTargets() { return getBodyUnitary().getTargets(); } Value getControl(size_t i); size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } static StringRef getBaseSymbol() { return "ctrl"; } }]; @@ -1005,13 +1014,16 @@ def InvOp : QCOp<"inv", let extraClassDeclaration = [{ [[nodiscard]] UnitaryOpInterface getBodyUnitary(); size_t getNumQubits() { return getBodyUnitary().getNumQubits(); } - size_t getNumTargets() { return getNumQubits(); } - static size_t getNumControls() { return 0; } + size_t getNumTargets() { return getBodyUnitary().getNumTargets(); } + size_t getNumControls() { return getBodyUnitary().getNumControls(); } Value getQubit(size_t i) { return getBodyUnitary().getQubit(i); } - Value getTarget(size_t i) { return getQubit(i); } - static Value getControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } + ValueRange getTargets() { return getBodyUnitary().getTargets(); } + Value getControl(size_t i) { return getBodyUnitary().getControl(i); } + ValueRange getControls() { return getBodyUnitary().getControls(); } size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } static StringRef getBaseSymbol() { return "inv"; } }]; diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index a09c49ea7a..6520d83702 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -118,7 +118,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Get a static qubit by index - * @param index The qubit index (must be non-negative) + * @param index The qubit index * @return A tracked, valid qubit SSA value * * @par Example: @@ -129,7 +129,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * %q0 = qco.static 0 : !qco.qubit * ``` */ - Value staticQubit(int64_t index); + Value staticQubit(uint64_t index); /** * @brief Allocate a qubit register @@ -1228,24 +1228,24 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { //===--------------------------------------------------------------------===// /** - * @brief Explicitly deallocate a qubit + * @brief Consume a qubit value (end of lifetime) * * @details * Validates and removes the qubit from tracking. Optional; `finalize()` - * automatically deallocates all remaining qubits. + * automatically sinks all remaining qubits. * - * @param qubit Qubit to deallocate (must be valid/unconsumed) + * @param qubit Qubit to sink (must be valid/unconsumed) * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * builder.dealloc(q); + * builder.sink(q); * ``` * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` */ - QCOProgramBuilder& dealloc(Value qubit); + QCOProgramBuilder& sink(Value qubit); //===--------------------------------------------------------------------===// // SCF operations diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 28a9034e80..efee9623e5 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -77,14 +77,20 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } -def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { - let summary = "Deallocate a qubit"; +def SinkOp : QCOOp<"sink", [MemoryEffects<[MemFree]>]> { + let summary = "Consume a qubit value (end of lifetime)"; let description = [{ - Deallocates a qubit, releasing its resources. + Consumes a qubit SSA value and marks the end of its lifetime. + + This operation is the canonical "sink" for QCO's linear/value semantics: + every qubit value must be consumed exactly once on all paths. + + When converting back to QC (reference semantics), sinks corresponding to + static qubits may be erased. Example: ```mlir - qco.dealloc %q : !qco.qubit + qco.sink %q : !qco.qubit ``` }]; diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h index cce4590efc..1a901882dc 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h @@ -104,8 +104,8 @@ template void walkUnit(Region& region, Fn&& fn) { .template Case([&](MeasureOp op) { qubits.remap(op.getQubitIn(), op.getQubitOut()); }) - .template Case( - [&](DeallocOp op) { qubits.remove(op.getQubit()); }); + .template Case( + [&](SinkOp op) { qubits.remove(op.getQubit()); }); } } } // namespace mlir::qco diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index 5599753f96..24e467b99c 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -406,7 +406,7 @@ struct ConvertJeffQubitAllocOpToQCO final }; /** - * @brief Converts jeff.qubit_free to qco.reset + qco.dealloc + * @brief Converts jeff.qubit_free to qco.reset + qco.sink * * @par Example: * ```mlir @@ -415,7 +415,7 @@ struct ConvertJeffQubitAllocOpToQCO final * is converted to * ```mlir * %q_out = qco.reset %q_in : !qco.qubit - * qco.dealloc %q_out : !qco.qubit + * qco.sink %q_out : !qco.qubit * ``` */ struct ConvertJeffQubitFreeOpToQCO final @@ -427,13 +427,13 @@ struct ConvertJeffQubitFreeOpToQCO final ConversionPatternRewriter& rewriter) const override { auto resetOp = qco::ResetOp::create(rewriter, op.getLoc(), adaptor.getInQubit()); - rewriter.replaceOpWithNewOp(op, resetOp.getQubitOut()); + rewriter.replaceOpWithNewOp(op, resetOp.getQubitOut()); return success(); } }; /** - * @brief Converts jeff.qubit_free_zero to qco.dealloc + * @brief Converts jeff.qubit_free_zero to qco.sink * * @par Example: * ```mlir @@ -441,7 +441,7 @@ struct ConvertJeffQubitFreeOpToQCO final * ``` * is converted to * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` */ struct ConvertJeffQubitFreeZeroOpToQCO final @@ -451,13 +451,13 @@ struct ConvertJeffQubitFreeZeroOpToQCO final LogicalResult matchAndRewrite(jeff::QubitFreeZeroOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getInQubit()); + rewriter.replaceOpWithNewOp(op, adaptor.getInQubit()); return success(); } }; /** - * @brief Converts jeff.qubit_measure to qco.measure + qco.dealloc + * @brief Converts jeff.qubit_measure to qco.measure + qco.sink * * @par Example: * ```mlir @@ -466,7 +466,7 @@ struct ConvertJeffQubitFreeZeroOpToQCO final * is converted to * ```mlir * %q_out, %result = qco.measure %q_in : !qco.qubit - * qco.dealloc %q_out : !qco.qubit + * qco.sink %q_out : !qco.qubit * ``` */ struct ConvertJeffQubitMeasureOpToQCO final @@ -479,7 +479,7 @@ struct ConvertJeffQubitMeasureOpToQCO final auto loc = op.getLoc(); auto measureOp = qco::MeasureOp::create(rewriter, loc, adaptor.getInQubit()); - qco::DeallocOp::create(rewriter, loc, measureOp.getQubitOut()); + qco::SinkOp::create(rewriter, loc, measureOp.getQubitOut()); rewriter.replaceOp(op, measureOp.getResult()); return success(); } diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index 124c095060..01ed0f82a8 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -269,23 +269,22 @@ struct ConvertQCOAllocOpToJeff final }; /** - * @brief Converts qco.dealloc to jeff.qubit_free_zero + * @brief Converts qco.sink to jeff.qubit_free_zero * * @par Example: * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` * is converted to * ```mlir * jeff.qubit_free_zero %q : !jeff.qubit * ``` */ -struct ConvertQCODeallocOpToJeff final - : StatefulOpConversionPattern { +struct ConvertQCOSinkOpToJeff final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(qco::SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); @@ -1364,7 +1363,7 @@ struct QCOToJeff final : impl::QCOToJeffBase { // Register operation conversion patterns jeff::populateNativeToJeffConversionPatterns(patterns); patterns.add< - ConvertQCOAllocOpToJeff, ConvertQCODeallocOpToJeff, + ConvertQCOAllocOpToJeff, ConvertQCOSinkOpToJeff, ConvertQCOMeasureOpToJeff, ConvertQCOResetOpToJeff, ConvertQCOGPhaseOpToJeff, ConvertQCOOneTargetZeroParameterToJeff, diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 381486b02b..7c1dc2baca 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include namespace mlir { @@ -33,6 +35,52 @@ namespace mlir { using namespace qco; using namespace qc; +namespace { +/** @brief Qubit addressing mode */ +enum class QubitAddressingMode : std::uint8_t { + Unknown, //!< The addressing mode is not known. + Static, //!< The module uses static qubit allocation. + Dynamic //!< The module uses dynamic qubit allocation. +}; + +/** + * @brief State object for tracking qubit addressing mode. + * + * @details + * Used to track whether a function uses static or dynamic qubit allocation. + * This is used to determine whether to convert `qco.sink` to `qc.dealloc` (for + * dynamic qubits) or simply erase it (for static qubits). This is also used to + * catch cases of mixed addressing being used, which is not supported. + */ +struct LoweringState { + /// The qubit addressing mode used in the module + QubitAddressingMode mode = QubitAddressingMode::Unknown; +}; + +/** + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which is used to track the addressing mode of the module. + * @tparam OpType The QCO operation type to be converted. + */ +template +class StatefulOpConversionPattern : public OpConversionPattern { + +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, + MLIRContext* context, LoweringState* state) + : OpConversionPattern(typeConverter, context), state_(state) {} + + /// Returns the shared lowering state object + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; +} // namespace + #define GEN_PASS_DEF_QCOTOQC #include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" @@ -80,12 +128,19 @@ class QCOToQCTypeConverter final : public TypeConverter { * %q = qc.alloc("q", 3, 0) : !qc.qubit * ``` */ -struct ConvertQCOAllocOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQCOAllocOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult matchAndRewrite(qco::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& qubitMode = getState().mode; + if (qubitMode == QubitAddressingMode::Unknown) { + qubitMode = QubitAddressingMode::Dynamic; + } + assert(qubitMode != QubitAddressingMode::Static && + "Static qubits cannot be mixed with dynamic qubits"); + // Create qc.alloc with preserved register metadata rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), @@ -96,28 +151,40 @@ struct ConvertQCOAllocOp final : OpConversionPattern { }; /** - * @brief Converts qco.dealloc to qc.dealloc + * @brief Converts qco.sink to qc.dealloc. * * @details - * Deallocates a qubit, releasing its resources. The OpAdaptor automatically - * provides the type-converted qubit operand (!qc.qubit instead of - * !qco.qubit), so we simply pass it through to the new operation. + * In QCO, qubits have value/linear semantics and must be consumed explicitly + * (via `qco.sink`). In QC, qubits have reference semantics; for dynamic qubits + * we materialize this end-of-lifetime as `qc.dealloc`. Static qubits do not + * need explicit deallocation, so we simply erase the `qco.sink` operation. + * + * The OpAdaptor automatically provides the type-converted qubit operand + * (`!qc.qubit` instead of `!qco.qubit`), so we simply pass it through to the + * new operation when needed. * * Example transformation: * ```mlir - * qco.dealloc %q_qco : !qco.qubit + * qco.sink %q_qco : !qco.qubit * // becomes: * qc.dealloc %q_qc : !qc.qubit * ``` */ -struct ConvertQCODeallocOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQCOSinkOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted qubit - rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + const auto mode = getState().mode; + assert(mode != QubitAddressingMode::Unknown && + "Sinks cannot exist without allocations"); + + if (mode == QubitAddressingMode::Static) { + rewriter.eraseOp(op); + return success(); + } + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); } }; @@ -137,12 +204,19 @@ struct ConvertQCODeallocOp final : OpConversionPattern { * %q = qc.static 0 : !qc.qubit * ``` */ -struct ConvertQCOStaticOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQCOStaticOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& qubitMode = getState().mode; + if (qubitMode == QubitAddressingMode::Unknown) { + qubitMode = QubitAddressingMode::Static; + } + assert(qubitMode != QubitAddressingMode::Dynamic && + "Dynamic qubits cannot be mixed with static qubits"); + // Create qc.static with the same index rewriter.replaceOpWithNewOp(op, op.getIndex()); return success(); @@ -747,6 +821,9 @@ struct QCOToQC final : impl::QCOToQCBase { MLIRContext* context = &getContext(); auto* module = getOperation(); + // Create state object to track the qubit addressing mode + LoweringState state; + ConversionTarget target(*context); RewritePatternSet patterns(context); QCOToQCTypeConverter typeConverter(context); @@ -755,10 +832,8 @@ struct QCOToQC final : impl::QCOToQCBase { target.addIllegalDialect(); target.addLegalDialect(); - // Register operation conversion patterns - // Note: No state tracking needed - OpAdaptors handle type conversion + // Register operation conversion patterns that do not need state tracking patterns.add< - ConvertQCOAllocOp, ConvertQCODeallocOp, ConvertQCOStaticOp, ConvertQCOMeasureOp, ConvertQCOResetOp, ConvertQCOZeroTargetOneParameterToQC, ConvertQCOOneTargetZeroParameterToQC, @@ -791,6 +866,10 @@ struct QCOToQC final : impl::QCOToQCBase { ConvertQCOBarrierOp, ConvertQCOCtrlOp, ConvertQCOInvOp, ConvertQCOYieldOp>(typeConverter, context); + // Register operation conversion patterns that need state tracking + patterns.add( + typeConverter, context, &state); + // Conversion of qco types in func.func signatures // Note: This currently has limitations with signature changes populateFunctionOpInterfaceTypeConversionPattern( diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 474f80aacf..23976d6760 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -16,12 +16,14 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include +#include #include +#include #include #include #include -#include #include +#include #include #include #include @@ -30,7 +32,6 @@ #include #include -#include #include namespace mlir { @@ -42,7 +43,6 @@ using namespace qc; #include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" namespace { - /** * @brief State object for tracking qubit value flow during conversion * @@ -72,13 +72,23 @@ namespace { * - %q2 after the X gate */ struct LoweringState { - /// Map from original QC qubit references to their latest QCO SSA values - llvm::DenseMap qubitMap; - - /// Modifier information - int64_t inNestedRegion = 0; - DenseMap> targetsIn; - DenseMap> targetsOut; + struct ModifierFrame { + /// QC qubits yielded from the current modifier region, in yield order. + SmallVector yieldOrder; + + /// Latest QCO SSA values for QC qubits that are remapped inside the + /// modifier region. + llvm::DenseMap currentQubits; + }; + + /// Per-region map from QC qubit references to latest QCO SSA values. + /// + /// @details Keys are `Operation::getParentRegion()` for ops being converted + /// (typically a `func.func` body, or a `qc.ctrl` / `qc.inv` region). + llvm::DenseMap> qubitMap; + + /// Stack of active modifier regions (`qc.ctrl` / `qc.inv`). + SmallVector modifierFrames; }; /** @@ -110,6 +120,189 @@ class StatefulOpConversionPattern : public OpConversionPattern { private: LoweringState* state_; }; +} // namespace + +/** + * @brief Helper function to look up the latest QCO qubit value for a given QC + * qubit reference + * + * @param qubitMap The mapping from QC qubits to QCO qubits for the current + * region + * @param qcQubit The QC qubit reference to look up + * @return The latest QCO qubit value corresponding to the given QC qubit + * reference + */ +[[nodiscard]] static Value +lookupMappedQubit(llvm::DenseMap& qubitMap, Value qcQubit) { + auto it = qubitMap.find(qcQubit); + assert(it != qubitMap.end() && "QC qubit not found"); + return it->second; +} + +/** @brief Returns whether lowering currently processes a modifier body. */ +[[nodiscard]] static bool isInsideModifier(const LoweringState& state) { + return !state.modifierFrames.empty(); +} + +/** @brief Returns the active modifier frame. */ +[[nodiscard]] static LoweringState::ModifierFrame& +currentModifierFrame(LoweringState& state) { + assert(isInsideModifier(state) && "expected active modifier frame"); + return state.modifierFrames.back(); +} + +/** @brief Finds the nearest region-local qubit map containing @p qcQubit. */ +[[nodiscard]] static llvm::DenseMap* +findMappedQubitMap(LoweringState& state, Operation* anchor, Value qcQubit) { + for (Region* current = anchor->getParentRegion(); current != nullptr; + current = current->getParentRegion()) { + auto mapIt = state.qubitMap.find(current); + if (mapIt != state.qubitMap.end() && mapIt->second.contains(qcQubit)) { + return &mapIt->second; + } + } + return nullptr; +} + +/** @brief Resolves the latest QCO SSA value for a QC qubit reference. */ +[[nodiscard]] static Value lookupMappedQubit(LoweringState& state, + Operation* anchor, Value qcQubit) { + if (isInsideModifier(state)) { + auto& frame = currentModifierFrame(state); + if (auto it = frame.currentQubits.find(qcQubit); + it != frame.currentQubits.end()) { + return it->second; + } + } + + auto* qubitMap = findMappedQubitMap(state, anchor, qcQubit); + assert(qubitMap != nullptr && "QC qubit not found"); + return lookupMappedQubit(*qubitMap, qcQubit); +} + +/** @brief Updates the latest QCO SSA value for a QC qubit reference. */ +static void assignMappedQubit(LoweringState& state, Operation* anchor, + Value qcQubit, Value qcoQubit) { + if (isInsideModifier(state)) { + auto& frame = currentModifierFrame(state); + if (auto it = frame.currentQubits.find(qcQubit); + it != frame.currentQubits.end()) { + it->second = qcoQubit; + return; + } + } + + if (auto* qubitMap = findMappedQubitMap(state, anchor, qcQubit)) { + (*qubitMap)[qcQubit] = qcoQubit; + return; + } + + state.qubitMap[anchor->getParentRegion()][qcQubit] = qcoQubit; +} + +/** @brief Resolves a range of QC qubits to their latest QCO values. */ +template +[[nodiscard]] static SmallVector +resolveMappedQubits(LoweringState& state, Operation* anchor, + const Range& qcQubits) { + return llvm::to_vector(llvm::map_range(qcQubits, [&](Value qcQubit) { + return lookupMappedQubit(state, anchor, qcQubit); + })); +} + +/** @brief Updates mappings for matching QC and QCO qubit ranges. */ +template +static void assignMappedQubits(LoweringState& state, Operation* anchor, + const QcRange& qcQubits, + const QcoRange& qcoQubits) { + for (auto [qcQubit, qcoQubit] : llvm::zip_equal(qcQubits, qcoQubits)) { + assignMappedQubit(state, anchor, qcQubit, qcoQubit); + } +} + +/** @brief Pushes a new modifier frame seeded with aliased target values. */ +static void pushModifierFrame(LoweringState& state, ValueRange qcTargets, + ValueRange qcoTargets) { + auto& [yieldOrder, currentQubits] = state.modifierFrames.emplace_back(); + llvm::append_range(yieldOrder, qcTargets); + for (auto [qcTarget, qcoTarget] : llvm::zip_equal(qcTargets, qcoTargets)) { + currentQubits.try_emplace(qcTarget, qcoTarget); + } +} + +/** @brief Pops the active modifier frame after lowering its yield. */ +static void popModifierFrame(LoweringState& state) { + assert(isInsideModifier(state) && "expected active modifier frame"); + state.modifierFrames.pop_back(); +} + +/** @brief Adds entry block aliases for modifier target values. */ +template +[[nodiscard]] static ValueRange addModifierAliases(OpType op, + const size_t numTargets, + PatternRewriter& rewriter) { + auto& entryBlock = op.getRegion().front(); + const auto opLoc = op.getLoc(); + const auto qubitType = qco::QubitType::get(op.getContext()); + rewriter.modifyOpInPlace(op, [&] { + for (size_t i = 0; i < numTargets; ++i) { + entryBlock.addArgument(qubitType, opLoc); + } + }); + return entryBlock.getArguments().take_back(numTargets); +} + +namespace { + +/** + * @brief Converts func.return and sinks remaining live qubits. + * + * @details + * QC uses reference semantics and does not enforce linear typing for qubits. + * After conversion, QCO requires that every qubit SSA value is consumed + * exactly once. For allocations (including static qubits), the sink is + * `qco.sink`. This pattern inserts `qco.sink` operations for all + * still-live qubits tracked in the lowering state right before the return. + */ +struct ConvertFuncReturnOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + Region* funcRegion = op->getParentRegion(); + auto& map = state.qubitMap[funcRegion]; + + // Build return values from qubitMap and collect live qubit information. + // A qubit from the current scope is considered alive if it is returned from + // the function. Otherwise, it is considered dead. + llvm::SmallVector returnValues; + returnValues.reserve(op.getNumOperands()); + llvm::DenseSet liveQubits; + for (auto [qcOperand, adaptorOperand] : + llvm::zip_equal(op.getOperands(), adaptor.getOperands())) { + if (auto it = map.find(qcOperand); it != map.end()) { + auto latest = it->second; + returnValues.emplace_back(latest); + liveQubits.insert(latest); + } else { + returnValues.emplace_back(adaptorOperand); + } + } + + // Deallocate dead qubit values + for (Value qcoQubit : llvm::make_second_range(map)) { + if (!liveQubits.contains(qcoQubit)) { + SinkOp::create(rewriter, op.getLoc(), qcoQubit); + } + } + state.qubitMap.erase(funcRegion); + + rewriter.replaceOpWithNewOp(op, returnValues); + return success(); + } +}; /** * @brief Type converter for QC-to-QCO conversion @@ -159,7 +352,8 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& state = getState(); + auto* operation = op.getOperation(); auto qcQubit = op.getResult(); // Create the qco.alloc operation with preserved register metadata @@ -171,25 +365,25 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { // Establish initial mapping: this QC qubit reference now corresponds // to this QCO SSA value - qubitMap.try_emplace(qcQubit, qcoQubit); + assignMappedQubit(state, operation, qcQubit, qcoQubit); return success(); } }; /** - * @brief Converts qc.dealloc to qco.dealloc + * @brief Converts qc.dealloc to qco.sink * * @details * Deallocates a qubit by looking up its latest QCO value and creating - * a corresponding qco.dealloc operation. The mapping is removed from + * a corresponding qco.sink operation. The mapping is removed from * the state as the qubit is no longer in use. * * Example transformation: * ```mlir * qc.dealloc %q : !qc.qubit * // becomes (where %q maps to %q_final): - * qco.dealloc %q_final : !qco.qubit + * qco.sink %q_final : !qco.qubit * ``` */ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { @@ -198,15 +392,15 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; - auto qcQubit = op.getQubit(); - - // Look up the latest QCO value for this QC qubit - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + auto& state = getState(); + auto* operation = op.getOperation(); + auto* region = operation->getParentRegion(); + auto& qubitMap = state.qubitMap[region]; + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); - // Create the dealloc operation - rewriter.replaceOpWithNewOp(op, qcoQubit); + // Create the sink operation + rewriter.replaceOpWithNewOp(op, qcoQubit); // Remove from state as qubit is no longer in use qubitMap.erase(qcQubit); @@ -236,20 +430,12 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; - auto qcQubit = op.getQubit(); - - // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); - - // Collect QCO qubit SSA value - auto qcoQubit = qcoOp.getQubit(); - - // Establish mapping from QC reference to QCO value - qubitMap[qcQubit] = qcoQubit; + auto& state = getState(); + auto* operation = op.getOperation(); + Value qcQubit = op.getQubit(); - // Replace the old operation result with the new result - rewriter.replaceOp(op, qcoQubit); + auto qcoOp = rewriter.replaceOpWithNewOp(op, op.getIndex()); + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubit()); return success(); } @@ -284,26 +470,21 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; - auto qcQubit = op.getQubit(); - - // Get the latest QCO qubit value from the state map - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + auto& state = getState(); + auto* operation = op.getOperation(); + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create qco.measure (returns both output qubit and bit result) auto qcoOp = qco::MeasureOp::create( rewriter, op.getLoc(), qcoQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - auto outQcoQubit = qcoOp.getQubitOut(); - auto newBit = qcoOp.getResult(); - // Update mapping: the QC qubit now corresponds to the output qubit - qubitMap[qcQubit] = outQcoQubit; + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); // Replace the QC operation's bit result with the QCO bit result - rewriter.replaceOp(op, newBit); + rewriter.replaceOp(op, qcoOp.getResult()); return success(); } @@ -335,18 +516,16 @@ struct ConvertQCResetOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; - auto qcQubit = op.getQubit(); - - // Get the latest QCO qubit value from the state map - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + auto& state = getState(); + auto* operation = op.getOperation(); + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create qco.reset (consumes input, produces output) auto qcoOp = qco::ResetOp::create(rewriter, op.getLoc(), qcoQubit); // Update mapping: the QC qubit now corresponds to the reset output - qubitMap[qcQubit] = qcoOp.getQubitOut(); + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); // Erase the old (it has no results to replace) rewriter.eraseOp(op); @@ -378,18 +557,8 @@ struct ConvertQCZeroTargetOneParameterToQCO final LogicalResult matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& state = this->getState(); - const auto inNestedRegion = state.inNestedRegion; - QCOOpType::create(rewriter, op.getLoc(), op.getParameter(0)); - // Update the state - if (inNestedRegion != 0) { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut; - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } - rewriter.eraseOp(op); return success(); @@ -420,32 +589,14 @@ struct ConvertQCOneTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -477,33 +628,15 @@ struct ConvertQCOneTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -535,33 +668,15 @@ struct ConvertQCOneTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -593,34 +708,16 @@ struct ConvertQCOneTargetThreeParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1), op.getParameter(2)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -653,40 +750,17 @@ struct ConvertQCTwoTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -719,41 +793,18 @@ struct ConvertQCTwoTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -786,41 +837,18 @@ struct ConvertQCTwoTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0), op.getParameter(1)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -848,25 +876,14 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap; - - // Get QCO qubits from state map - auto qcQubits = op.getQubits(); - SmallVector qcoQubits; - qcoQubits.reserve(qcQubits.size()); - for (const auto& qcQubit : qcQubits) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubits.push_back(qubitMap[qcQubit]); - } + auto* operation = op.getOperation(); + const auto qcQubits = llvm::to_vector(op.getQubits()); + auto qcoQubits = resolveMappedQubits(state, operation, qcQubits); // Create qco.barrier auto qcoOp = qco::BarrierOp::create(rewriter, op.getLoc(), qcoQubits); - // Update the state map - for (const auto& [qcQubit, qcoQubitOut] : - llvm::zip(qcQubits, qcoOp.getQubitsOut())) { - qubitMap[qcQubit] = qcoQubitOut; - } + assignMappedQubits(state, operation, qcQubits, qcoOp.getQubitsOut()); rewriter.eraseOp(op); return success(); @@ -897,48 +914,19 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap; - - // Get QCO controls from state map - auto qcControls = op.getControls(); - SmallVector qcoControls; - qcoControls.reserve(qcControls.size()); - for (const auto& qcControl : qcControls) { - assert(qubitMap.contains(qcControl) && "QC qubit not found"); - qcoControls.push_back(qubitMap[qcControl]); - } - - // Get QCO targets from state map + auto* operation = op.getOperation(); const auto numTargets = op.getNumTargets(); - SmallVector qcoTargets; - qcoTargets.reserve(numTargets); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - assert(qubitMap.contains(qcTarget) && "QC qubit not found"); - auto qcoTarget = qubitMap[qcTarget]; - qcoTargets.push_back(qcoTarget); - } + const auto qcControls = op.getControls(); + const auto qcTargets = op.getTargets(); + auto qcoControls = resolveMappedQubits(state, operation, qcControls); + auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); // Create qco.ctrl auto qcoOp = qco::CtrlOp::create(rewriter, op.getLoc(), qcoControls, qcoTargets); - // Update the state map if this is a top-level CtrlOp - // Nested CtrlOps are managed via the targetsIn and targetsOut maps - if (state.inNestedRegion == 0) { - for (const auto& [qcControl, qcoControl] : - llvm::zip(qcControls, qcoOp.getControlsOut())) { - qubitMap[qcControl] = qcoControl; - } - auto qcoTargetsOut = qcoOp.getTargetsOut(); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - qubitMap[qcTarget] = qcoTargetsOut[i]; - } - } - - // Update modifier information - state.inNestedRegion++; + assignMappedQubits(state, operation, qcControls, qcoOp.getControlsOut()); + assignMappedQubits(state, operation, qcTargets, qcoOp.getTargetsOut()); // Clone body region from QC to QCO auto& dstRegion = qcoOp.getRegion(); @@ -948,16 +936,8 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC ctrl region unexpectedly has entry block arguments"); - SmallVector qcoTargetAliases; - qcoTargetAliases.reserve(numTargets); - const auto qubitType = qco::QubitType::get(qcoOp.getContext()); - const auto opLoc = op.getLoc(); - rewriter.modifyOpInPlace(qcoOp, [&] { - for (auto i = 0UL; i < numTargets; i++) { - qcoTargetAliases.emplace_back(entryBlock.addArgument(qubitType, opLoc)); - } - }); - state.targetsIn[state.inNestedRegion] = std::move(qcoTargetAliases); + pushModifierFrame(state, qcTargets, + addModifierAliases(qcoOp, numTargets, rewriter)); rewriter.eraseOp(op); return success(); @@ -987,61 +967,27 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::InvOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap, inNestedRegion, targetsIn, targetsOut] = getState(); - - // Get QCO targets from state map + auto& state = getState(); + auto* operation = op.getOperation(); const auto numTargets = op.getNumTargets(); - SmallVector qcoTargets; - if (inNestedRegion == 0) { - qcoTargets.reserve(numTargets); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - assert(qubitMap.contains(qcTarget) && "QC qubit not found"); - qcoTargets.emplace_back(qubitMap[qcTarget]); - } - } else { - assert(targetsIn[inNestedRegion].size() == numTargets && - "Invalid number of input targets"); - qcoTargets = targetsIn[inNestedRegion]; - } + const auto qcTargets = op.getTargets(); + auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); // Create qco.inv auto qcoOp = qco::InvOp::create(rewriter, op.getLoc(), qcoTargets); - // Update state map - if (inNestedRegion == 0) { - const auto qubitsOut = qcoOp.getQubitsOut(); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - qubitMap[qcTarget] = qubitsOut[i]; - } - } else { - targetsIn.erase(inNestedRegion); - targetsOut.try_emplace(inNestedRegion, qcoOp.getQubitsOut()); - } - - // Update modifier information - inNestedRegion++; + assignMappedQubits(state, operation, qcTargets, qcoOp.getQubitsOut()); // Clone body region from QC to QCO auto& dstRegion = qcoOp.getRegion(); rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end()); - // Create block arguments for target qubits and store them in - // `state.targetsIn`. + // Create block arguments for target qubits and seed the nested frame. auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC inv region unexpectedly has entry block arguments"); - SmallVector qcoTargetAliases; - qcoTargetAliases.reserve(numTargets); - const auto qubitType = qco::QubitType::get(qcoOp.getContext()); - const auto opLoc = op.getLoc(); - rewriter.modifyOpInPlace(qcoOp, [&] { - for (auto i = 0UL; i < numTargets; i++) { - qcoTargetAliases.emplace_back(entryBlock.addArgument(qubitType, opLoc)); - } - }); - targetsIn[inNestedRegion] = std::move(qcoTargetAliases); + pushModifierFrame(state, qcTargets, + addModifierAliases(qcoOp, numTargets, rewriter)); rewriter.eraseOp(op); return success(); @@ -1067,10 +1013,11 @@ struct ConvertQCYieldOp final : StatefulOpConversionPattern { matchAndRewrite(qc::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - const auto& targets = state.targetsOut[state.inNestedRegion]; + auto* operation = op.getOperation(); + auto& frame = currentModifierFrame(state); + auto targets = resolveMappedQubits(state, operation, frame.yieldOrder); rewriter.replaceOpWithNewOp(op, targets); - state.targetsOut.erase(state.inNestedRegion); - state.inNestedRegion--; + popModifierFrame(state); return success(); } }; @@ -1158,9 +1105,18 @@ struct QCToQCO final : impl::QCToQCOBase { }); // Conversion of qc types in func.return - populateReturnOpTypeConversionPattern(patterns, typeConverter); - target.addDynamicallyLegalOp( - [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + // + // Note: `func.return` may already be type-legal even though we still need + // to insert sink operations (`qco.sink`) for dead qubit values. Therefore, + // we mark it illegal as long as the qubit map of the region is not empty. + patterns.add(typeConverter, context, &state); + target.addDynamicallyLegalOp([&](const func::ReturnOp op) { + if (!typeConverter.isLegal(op)) { + return false; + } + const auto it = state.qubitMap.find(op->getParentRegion()); + return it == state.qubitMap.end() || it->second.empty(); + }); // Conversion of qc types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index da5cf312dd..991dba1c1b 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -79,16 +79,10 @@ Value QCProgramBuilder::allocQubit() { return qubit; } -Value QCProgramBuilder::staticQubit(const int64_t index) { +Value QCProgramBuilder::staticQubit(const uint64_t index) { checkFinalized(); - if (index < 0) { - llvm::reportFatalUsageError("Index must be non-negative"); - } - - // Create the StaticOp with the given index - auto indexAttr = getI64IntegerAttr(index); - auto staticOp = StaticOp::create(*this, indexAttr); + auto staticOp = StaticOp::create(*this, index); return staticOp.getQubit(); } diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 05b55ff6dc..372498cb0c 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -83,15 +83,10 @@ Value QCOProgramBuilder::allocQubit() { return qubit; } -Value QCOProgramBuilder::staticQubit(const int64_t index) { +Value QCOProgramBuilder::staticQubit(const uint64_t index) { checkFinalized(); - if (index < 0) { - llvm::reportFatalUsageError("Index must be non-negative"); - } - - auto indexAttr = getI64IntegerAttr(index); - auto staticOp = StaticOp::create(*this, indexAttr); + auto staticOp = StaticOp::create(*this, index); const auto qubit = staticOp.getQubit(); // Track the static qubit as valid @@ -764,12 +759,13 @@ std::pair QCOProgramBuilder::ctrl( // Update tracking const auto& controlsOut = ctrlOp.getControlsOut(); - for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + for (const auto& [control, controlOut] : + llvm::zip_equal(controls, controlsOut)) { updateQubitTracking(control, controlOut); } const auto& targetsOut = ctrlOp.getTargetsOut(); for (const auto& [target, targetOut] : - llvm::zip(innerTargetsOut, targetsOut)) { + llvm::zip_equal(innerTargetsOut, targetsOut)) { updateQubitTracking(target, targetOut); } @@ -805,7 +801,7 @@ ValueRange QCOProgramBuilder::inv( // Update tracking const auto& targetsOut = invOp.getQubitsOut(); for (const auto& [target, targetOut] : - llvm::zip(innerTargetsOut, targetsOut)) { + llvm::zip_equal(innerTargetsOut, targetsOut)) { updateQubitTracking(target, targetOut); } @@ -816,13 +812,13 @@ ValueRange QCOProgramBuilder::inv( // Deallocation //===----------------------------------------------------------------------===// -QCOProgramBuilder& QCOProgramBuilder::dealloc(Value qubit) { +QCOProgramBuilder& QCOProgramBuilder::sink(Value qubit) { checkFinalized(); validateQubitValue(qubit); validQubits.erase(qubit); - DeallocOp::create(*this, qubit); + SinkOp::create(*this, qubit); return *this; } @@ -924,35 +920,15 @@ OwningOpRef QCOProgramBuilder::finalize() { "Insertion point is not in entry block of main function"); } - auto blockOrderComparator = [](Value a, Value b) { - auto* opA = a.getDefiningOp(); - auto* opB = b.getDefiningOp(); - if (!opA || !opB || opA->getBlock() != opB->getBlock()) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); - } - return opA->isBeforeInBlock(opB); - }; - // Automatically deallocate all still-allocated qubits - // Sort qubits for deterministic output - llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); - llvm::sort(sortedQubits, blockOrderComparator); - - for (auto qubit : sortedQubits) { - DeallocOp::create(*this, qubit); + for (auto qubit : validQubits) { + SinkOp::create(*this, qubit); } + validQubits.clear(); - // Automatically deallocate all still-allocated tensors - // Sort tensors for deterministic output - llvm::SmallVector sortedTensors(validTensors.begin(), - validTensors.end()); - llvm::sort(sortedTensors, blockOrderComparator); - - for (auto tensor : sortedTensors) { + for (auto tensor : validTensors) { qtensor::DeallocOp::create(*this, tensor); } - - validQubits.clear(); validTensors.clear(); // Create constant 0 for successful exit code diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp similarity index 58% rename from mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp rename to mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index 0de300cb7d..de6ee6edc7 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -22,21 +21,19 @@ using namespace mlir::qco; namespace { /** - * @brief Remove matching allocation and deallocation pairs without operations + * @brief Remove matching alloc/static and sink pairs without operations * between them. */ -struct RemoveAllocDeallocPair final : OpRewritePattern { +struct RemoveAllocSinkPair final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(DeallocOp op, + LogicalResult matchAndRewrite(SinkOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an AllocOp or a StaticOp auto* defOp = op.getQubit().getDefiningOp(); - if (!llvm::isa(defOp)) { + if (!llvm::isa_and_nonnull(defOp)) { return failure(); } - // Remove the AllocOp/StaticOp and the DeallocOp rewriter.eraseOp(op); rewriter.eraseOp(defOp); return success(); @@ -45,7 +42,7 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } // namespace -void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); +void SinkOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); } diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index db6ebe2606..0682b1dd87 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -715,11 +715,11 @@ struct MappingPass : impl::MappingPassBase { return WalkResult::interrupt(); }) - .template Case([&](auto) { - std::ranges::advance(it, step); - return WalkResult::advance(); - }) + .template Case( + [&](auto) { + std::ranges::advance(it, step); + return WalkResult::advance(); + }) .Default([&](Operation* op) { report_fatal_error("unknown op in wireiterator use: " + op->getName().getStringRef()); @@ -797,7 +797,7 @@ struct MappingPass : impl::MappingPassBase { const auto hw = layout.getHardwareIndex(p); auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), hw); rewriter.setInsertionPoint(funcBody.back().getTerminator()); - DeallocOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); + SinkOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); } } diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index 7d91cd88c3..c9f60b1eb3 100644 --- a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp +++ b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp @@ -24,8 +24,8 @@ namespace mlir::qco { mlir::Value WireIterator::qubit() const { - // A deallocation doesn't have an OpResult. - if (op_ != nullptr && mlir::isa(op_)) { + // A sink/deallocation doesn't have an OpResult. + if (op_ != nullptr && mlir::isa(op_)) { return nullptr; } return qubit_; @@ -41,8 +41,8 @@ void WireIterator::forward() { assert(qubit_.getNumUses() == 1 && "expected linear typing"); op_ = *(qubit_.getUsers().begin()); - // A deallocation op defines the end of the qubit wire (dynamic and static). - if (mlir::isa(op_)) { + // A sink op defines the end of the qubit wire (dynamic and static). + if (mlir::isa(op_)) { isSentinel_ = true; return; } @@ -69,8 +69,9 @@ void WireIterator::backward() { return; } - // For deallocations, qubit_ is an OpOperand. Hence, only get the def-op. - if (mlir::isa(op_)) { + // For sinks/deallocations, qubit_ is an OpOperand. Hence, only get the + // def-op. + if (mlir::isa(op_)) { op_ = qubit_.getDefiningOp(); return; } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index a81f6a7c01..184bb7d677 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -193,6 +193,31 @@ INSTANTIATE_TEST_SUITE_P( "StaticQubits", nullptr, MQT_NAMED_BUILDER(mlir::qc::staticQubits), MQT_NAMED_BUILDER(mlir::qc::staticQubits), MQT_NAMED_BUILDER(mlir::qir::staticQubits), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithParametricOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithParametricOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithTwoTargetOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithTwoTargetOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithCtrl", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithCtrl), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithInv", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithInv), false}, CompilerPipelineTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubit), @@ -415,8 +440,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::p), MQT_NAMED_BUILDER(mlir::qir::p)}, CompilerPipelineTestCase{ - "SingleControlledP", MQT_NAMED_BUILDER(qc::singleControlledP), - nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledP), + "SingleControlledP", + MQT_NAMED_BUILDER(qc::singleControlledP), nullptr, + MQT_NAMED_BUILDER(mlir::qc::singleControlledP), MQT_NAMED_BUILDER(mlir::qir::singleControlledP)}, CompilerPipelineTestCase{ "MultipleControlledP", MQT_NAMED_BUILDER(qc::multipleControlledP), diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index bb81b94ecc..86133967cb 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -113,6 +113,33 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { areModulesEquivalentWithPermutations(program.get(), reference.get())); } +/// \name QCOToQC/QubitManagement/QubitManagement.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCOQubitManagementTest, QCOToQCTest, + testing::Values( + QCOToQCTestCase{"StaticQubits", MQT_NAMED_BUILDER(qco::staticQubits), + MQT_NAMED_BUILDER(qc::staticQubits)}, + QCOToQCTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithOps)}, + QCOToQCTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps)}, + QCOToQCTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps)}, + QCOToQCTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl)}, + QCOToQCTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qco::staticQubitsWithInv), + MQT_NAMED_BUILDER(qc::staticQubitsWithInv)}, + QCOToQCTestCase{"AllocDeallocPair", + MQT_NAMED_BUILDER(qco::allocSinkPair), + MQT_NAMED_BUILDER(qc::allocDeallocPair)})); +/// @} + /// \name QCOToQC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 98d6c9e012..ce017e956f 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -112,6 +112,33 @@ TEST_P(QCToQCOTest, ProgramEquivalence) { areModulesEquivalentWithPermutations(program.get(), reference.get())); } +/// \name QCToQCO/QubitManagement/StaticOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCStaticOpTest, QCToQCOTest, + testing::Values( + QCToQCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), + MQT_NAMED_BUILDER(qco::staticQubits)}, + QCToQCOTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithOps)}, + QCToQCOTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps)}, + QCToQCOTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps)}, + QCToQCOTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl)}, + QCToQCOTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(qco::staticQubitsWithInv)}, + QCToQCOTestCase{"AllocDeallocPair", + MQT_NAMED_BUILDER(qc::allocDeallocPair), + MQT_NAMED_BUILDER(qco::allocSinkPair)})); +/// @} + /// \name QCToQCO/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index 82ab251e41..ef938ce4e7 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -624,6 +624,21 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qir::emptyQIR)}, QCToQIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), MQT_NAMED_BUILDER(qir::emptyQIR)}, + QCToQIRTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithOps)}, + QCToQIRTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithParametricOps)}, + QCToQIRTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithTwoTargetOps)}, + QCToQIRTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qir::staticQubitsWithCtrl)}, + QCToQIRTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(qir::staticQubitsWithInv)}, QCToQIRTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 221b750f7e..2a58f028e2 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -899,6 +899,21 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQC)}, QCTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QCTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QCTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QCTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QCTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)}, QCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQC)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index ad4981d1b7..8c0c54d2ed 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -108,7 +108,7 @@ TEST_F(QCOTest, DirectIfBuilder) { auto innerQubit = XOp::create(builder, qubits[0]); return llvm::SmallVector{innerQubit}; }); - DeallocOp::create(builder, ifOp.getResult(0)); + SinkOp::create(builder, ifOp.getResult(0)); auto directBuilder = builder.finalize(); ASSERT_TRUE(directBuilder); @@ -1060,7 +1060,22 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), + QCOTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QCOTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QCOTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QCOTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QCOTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)}, + QCOTestCase{"AllocSinkPair", MQT_NAMED_BUILDER(allocSinkPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt index 3ea628104a..1ced00dc34 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt @@ -14,6 +14,7 @@ target_link_libraries( PRIVATE MLIRParser GTest::gtest_main MLIRQCProgramBuilder + MLIRQCOProgramBuilder MLIRQCOUtils MLIRQCToQCO MLIRQCOToQC diff --git a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp index 39b16525bd..80622812d0 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp @@ -57,10 +57,10 @@ TEST_F(DriversTest, FullWalk) { const auto [q22, c2] = builder.measure(q21); const auto [q32, c3] = builder.measure(q31); - builder.dealloc(q03); - builder.dealloc(q12); - builder.dealloc(q22); - builder.dealloc(q32); + builder.sink(q03); + builder.sink(q12); + builder.sink(q22); + builder.sink(q32); auto module = builder.finalize(); auto func = *(module->getOps().begin()); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp index fdc2b758da..7eb59db509 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp @@ -52,8 +52,8 @@ TEST_P(WireIteratorTest, MixedUse) { const auto [q02, q11] = builder.cx(q01, q10); const auto [q03, c0] = builder.measure(q02); const auto q04 = builder.reset(q03); - builder.dealloc(q04); - builder.dealloc(q11); + builder.sink(q04); + builder.sink(q11); [[maybe_unused]] auto module = builder.finalize(); // Setup WireIterator. @@ -83,7 +83,7 @@ TEST_P(WireIteratorTest, MixedUse) { ASSERT_EQ(it.qubit(), q04); ++it; - ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.dealloc + ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.sink ASSERT_EQ(it.qubit(), nullptr); ++it; @@ -97,7 +97,7 @@ TEST_P(WireIteratorTest, MixedUse) { // --it; - ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.dealloc + ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.sink ASSERT_EQ(it.qubit(), nullptr); --it; diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index e177c90329..66f063b069 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -522,17 +522,31 @@ INSTANTIATE_TEST_SUITE_P( /// @{ INSTANTIATE_TEST_SUITE_P( QIRQubitManagementTest, QIRTest, - testing::Values(QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(allocQubit)}, - QIRTestCase{"AllocQubitRegister", - MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(allocQubitRegister)}, - QIRTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, - QIRTestCase{"AllocLargeRegister", - MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(allocLargeRegister)}, - QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), - MQT_NAMED_BUILDER(staticQubits)})); + testing::Values( + QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), + MQT_NAMED_BUILDER(allocQubit)}, + QIRTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), + MQT_NAMED_BUILDER(allocQubitRegister)}, + QIRTestCase{"AllocMultipleQubitRegisters", + MQT_NAMED_BUILDER(allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, + QIRTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), + MQT_NAMED_BUILDER(allocLargeRegister)}, + QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + MQT_NAMED_BUILDER(staticQubits)}, + QIRTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QIRTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QIRTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QIRTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QIRTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)})); /// @} diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 2134b1f368..7e6acc8b90 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -34,6 +34,37 @@ void staticQubits(QCProgramBuilder& b) { b.staticQubit(1); } +void staticQubitsWithOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.h(q0); + b.h(q1); +} + +void staticQubitsWithParametricOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rx(std::numbers::pi / 4., q0); + b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rzz(0.123, q0, q1); +} + +void staticQubitsWithCtrl(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.cx(q0, q1); +} + +void staticQubitsWithInv(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + b.inv([&]() { b.t(q0); }); +} + void allocDeallocPair(QCProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 21225c5b44..e48c1c4404 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -33,6 +33,21 @@ void allocLargeRegister(QCProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QCProgramBuilder& b); + +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QCProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QCProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QCProgramBuilder& b); + +/// Allocates a static qubit and applies an inverse modifier. +void staticQubitsWithInv(QCProgramBuilder& b); + /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 1ef16df6d0..e7695e3cac 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -38,9 +38,42 @@ void staticQubits(QCOProgramBuilder& b) { b.staticQubit(1); } -void allocDeallocPair(QCOProgramBuilder& b) { +void staticQubitsWithOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + q0 = b.h(q0); + q1 = b.h(q1); +} + +void staticQubitsWithParametricOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + q0 = b.rx(std::numbers::pi / 4., q0); + q1 = b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + std::tie(q0, q1) = b.rzz(0.123, q0, q1); +} + +void staticQubitsWithCtrl(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + std::tie(q0, q1) = b.cx(q0, q1); +} + +void staticQubitsWithInv(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + q0 = b.inv({q0}, [&](auto targets) -> llvm::SmallVector { + return {b.t(targets[0])}; + })[0]; +} + +void allocSinkPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); - b.dealloc(q); + b.sink(q); } void singleMeasurementToSingleBit(QCOProgramBuilder& b) { diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index ba26e42969..a14100102c 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -33,8 +33,23 @@ void allocLargeRegister(QCOProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCOProgramBuilder& b); -/// Allocates and explicitly deallocates a single qubit. -void allocDeallocPair(QCOProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QCOProgramBuilder& b); + +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QCOProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QCOProgramBuilder& b); + +/// Allocates a static qubit and applies an inverse modifier. +void staticQubitsWithInv(QCOProgramBuilder& b); + +/// Allocates and explicitly sinks a single qubit. +void allocSinkPair(QCOProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 883cf59af8..8674715ca7 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" +#include + namespace mlir::qir { void emptyQIR([[maybe_unused]] QIRProgramBuilder& builder) {} @@ -32,6 +34,37 @@ void staticQubits(QIRProgramBuilder& b) { b.staticQubit(1); } +void staticQubitsWithOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.h(q0); + b.h(q1); +} + +void staticQubitsWithParametricOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rx(std::numbers::pi / 4., q0); + b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rzz(0.123, q0, q1); +} + +void staticQubitsWithCtrl(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.cx(q0, q1); +} + +void staticQubitsWithInv(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + b.tdg(q0); +} + void singleMeasurementToSingleBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index f379f19785..927a935286 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -33,6 +33,21 @@ void allocLargeRegister(QIRProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QIRProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QIRProgramBuilder& b); + +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QIRProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QIRProgramBuilder& b); + +/// Allocates a static qubit and applies the inverse of a T gate (Tdg). +void staticQubitsWithInv(QIRProgramBuilder& b); + // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit.