diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d4b909ac..24010955a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,7 +100,7 @@ releases may include breaking changes. [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], [#1781], [#1782], [#1806], [#1807], [#1815], [#1808], [#1824], [#1869], [#1872], [#1886], [#1914], [#1925], [#1927], [#1935], [#1936], [#1938], [#1975], - [#1976], [#2006], [#2026]) ([**@burgholzer**], [**@denialhaag**], + [#1976], [#2006], [#2014], [#2026]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**]) @@ -726,6 +726,7 @@ for previous changelogs._ [#2026]: https://github.com/munich-quantum-toolkit/core/pull/2026 +[#2014]: https://github.com/munich-quantum-toolkit/core/pull/2014 [#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011 [#2010]: https://github.com/munich-quantum-toolkit/core/pull/2010 [#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007 diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index c7e15cec18..ec02c2053c 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -10,6 +10,7 @@ #pragma once +#include #include #include #include @@ -206,6 +207,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Allocate a qubit register and eagerly load every element * @param size Number of qubits (must be positive) + * @param name Optional source-level register name * @return A `QubitRegister` containing the backing memref and one reference * for every eagerly loaded element * @@ -220,18 +222,19 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * %q2 = memref.load %memref[%c2] : memref<3x!qc.qubit> * ``` */ - QubitRegister allocQubitRegister(int64_t size); + QubitRegister allocQubitRegister(int64_t size, StringRef name = {}); /** * @brief Allocate storage for a qubit register without loading its elements * @param size Number of qubits (must be positive) + * @param name Optional source-level register name * @return The memref value representing the qubit register * * @details The register is tracked for automatic deallocation and remains * intact until an element is loaded. Use `loadQubit` to obtain references at * their points of use. */ - Value allocQubitRegisterStorage(int64_t size); + Value allocQubitRegisterStorage(int64_t size, StringRef name = {}); /** * @brief Explicitly loads a qubit from a memref @@ -1407,6 +1410,9 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { /// Track allocated memrefs for automatic deallocation DenseSet allocatedQregs; + /// Track non-empty source-level qubit register names. + llvm::StringSet<> qubitRegisterNames; + /// Check if the builder has been finalized void checkFinalized() const; diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index a1c7e0bfe6..6647ddcee6 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -10,6 +10,7 @@ #pragma once +#include #include #include #include @@ -212,6 +213,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Allocate a qubit tensor and eagerly extract every element * @param size Number of qubits (must be positive) + * @param name Optional source-level register name * @return A `QubitRegister` containing the residual tensor and one standalone * qubit value for every eagerly extracted element * @@ -226,7 +228,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * %t3, %q2 = qtensor.extract %t2[%c2]: tensor<3x!qco.qubit> * ``` */ - QubitRegister allocQubitRegister(int64_t size); + QubitRegister allocQubitRegister(int64_t size, StringRef name = {}); /** * @brief Allocate a classical bit register @@ -1823,6 +1825,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { MLIRContext* ctx{}; Operation* module; + /// Track non-empty source-level qubit register names. + llvm::StringSet<> qubitRegisterNames; + /// Check if the builder has been finalized void checkFinalized() const; diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 150ca59f6d..430b92b5b1 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -45,6 +45,10 @@ namespace mlir::utils { inline constexpr llvm::StringLiteral CLASSICAL_REGISTER_NAME_ATTR = "mqt.classical_register_name"; +/// Attribute used to retain a source-level qubit-register name. +inline constexpr llvm::StringLiteral QUBIT_REGISTER_NAME_ATTR = + "mqt.qubit_register_name"; + /// Check if a floating-point value is an integer. [[nodiscard]] inline bool isIntegerExponent(double r) { return r == std::floor(r) && std::isfinite(r); diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 80f7a2d5a1..271ad23465 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -18,6 +18,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -279,14 +280,20 @@ struct ConvertQTensorAllocOp final auto tensorType = cast(op.getResult().getType()); auto memrefType = MemRefType::get(tensorType.getShape(), qubitType); + const auto registerName = op->getAttr(utils::QUBIT_REGISTER_NAME_ATTR); + memref::AllocOp alloc; if (tensorType.hasStaticShape()) { // Static size: no dynamic size operand needed - rewriter.replaceOpWithNewOp(op, memrefType); + alloc = memref::AllocOp::create(rewriter, op.getLoc(), memrefType); } else { // Dynamic size: forward the runtime size operand - rewriter.replaceOpWithNewOp(op, memrefType, - op.getSize()); + alloc = memref::AllocOp::create(rewriter, op.getLoc(), memrefType, + op.getSize()); } + if (registerName) { + alloc->setAttr(utils::QUBIT_REGISTER_NAME_ATTR, registerName); + } + rewriter.replaceOp(op, alloc.getResult()); return success(); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 398f8f0fc8..e657ad541f 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -19,6 +19,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -838,21 +839,25 @@ struct ConvertMemRefAllocOp final return failure(); } - Value qtensor; + const auto registerName = op->getAttr(utils::QUBIT_REGISTER_NAME_ATTR); + qtensor::AllocOp alloc; if (shape[0] == ShapedType::kDynamic) { - qtensor = rewriter.replaceOpWithNewOp( - op, adaptor.getDynamicSizes()[0]); + alloc = qtensor::AllocOp::create(rewriter, op.getLoc(), + adaptor.getDynamicSizes()[0]); } else { auto size = arith::ConstantIndexOp::create(rewriter, op.getLoc(), shape[0]); - qtensor = - rewriter.replaceOpWithNewOp(op, size.getResult()); + alloc = qtensor::AllocOp::create(rewriter, op.getLoc(), size.getResult()); + } + if (registerName) { + alloc->setAttr(utils::QUBIT_REGISTER_NAME_ATTR, registerName); } auto& state = getState(); auto memref = op.getResult(); - assignMappedTensor(state, qtensor.getDefiningOp(), - lookupRegisterId(state, memref), qtensor); + assignMappedTensor(state, alloc, lookupRegisterId(state, memref), + alloc.getResult()); + rewriter.replaceOp(op, alloc.getResult()); return success(); } diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 9f5da975aa..94a3ef1675 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -124,8 +124,8 @@ Value QCProgramBuilder::staticQubit(const uint64_t index) { } QCProgramBuilder::QubitRegister -QCProgramBuilder::allocQubitRegister(const int64_t size) { - auto memref = allocQubitRegisterStorage(size); +QCProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { + auto memref = allocQubitRegisterStorage(size, name); SmallVector qubits; qubits.reserve(size); @@ -137,16 +137,24 @@ QCProgramBuilder::allocQubitRegister(const int64_t size) { return {.value = memref, .qubits = std::move(qubits)}; } -Value QCProgramBuilder::allocQubitRegisterStorage(const int64_t size) { +Value QCProgramBuilder::allocQubitRegisterStorage(const int64_t size, + const StringRef name) { checkFinalized(); ensureAllocationMode(AllocationMode::Dynamic); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } + if (!name.empty() && !qubitRegisterNames.insert(name).second) { + llvm::reportFatalUsageError("Qubit register names must be unique"); + } auto memrefType = MemRefType::get({size}, QubitType::get(ctx)); - auto memref = memref::AllocOp::create(*this, memrefType).getResult(); + auto alloc = memref::AllocOp::create(*this, memrefType); + if (!name.empty()) { + alloc->setAttr(QUBIT_REGISTER_NAME_ATTR, getStringAttr(name)); + } + auto memref = alloc.getResult(); allocatedQregs.insert(memref); return memref; } diff --git a/mlir/lib/Dialect/QC/Translation/OpenQASMToQCEmitter.cpp b/mlir/lib/Dialect/QC/Translation/OpenQASMToQCEmitter.cpp index 85af07058e..887bbbf76d 100644 --- a/mlir/lib/Dialect/QC/Translation/OpenQASMToQCEmitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/OpenQASMToQCEmitter.cpp @@ -2422,7 +2422,7 @@ class OpenQASMToQCEmitter { return; } qubitValues[statement.reg] = builder.allocQubitRegisterStorage( - static_cast(declaration.width)); + static_cast(declaration.width), declaration.name); return; } diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQCToOpenQASM3.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQCToOpenQASM3.cpp index 1730175945..684ba5acf3 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQCToOpenQASM3.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQCToOpenQASM3.cpp @@ -206,6 +206,13 @@ class OpenQASMEmitter { return uniqueName("out", nextScalar); } + [[nodiscard]] std::string qubitRegisterName(const StringRef requested) { + if (isValidOutputName(requested) && usedNames.insert(requested).second) { + return requested.str(); + } + return uniqueName("q", nextQubit); + } + [[nodiscard]] LogicalResult preflight() { SmallVector functions(moduleOp.getOps()); if (functions.size() != 1) { @@ -301,7 +308,12 @@ class OpenQASMEmitter { return fail(alloc, "only qubit and i1 memrefs are supported"); } if (resource.kind == ResourceKind::Qubit) { - resource.name = uniqueName("q", nextQubit); + StringRef requested; + if (const auto attr = alloc->getAttrOfType( + utils::QUBIT_REGISTER_NAME_ATTR)) { + requested = attr.getValue(); + } + resource.name = qubitRegisterName(requested); } else { resource.output = returnedMemrefs.contains(alloc.getResult()); StringRef requested; diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index 6467971fd3..144f139d65 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -139,8 +139,8 @@ allocateQregs(QCProgramBuilder& builder, // Allocate quantum registers using the builder SmallVector qregs; for (const auto* qregPtr : qregPtrs) { - auto qubitRegister = - builder.allocQubitRegister(static_cast(qregPtr->getSize())); + auto qubitRegister = builder.allocQubitRegister( + static_cast(qregPtr->getSize()), qregPtr->getName()); qregs.emplace_back(qregPtr, std::move(qubitRegister.qubits)); } diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 46fda77441..ceb7bf8505 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -134,14 +134,22 @@ Value QCOProgramBuilder::staticQubit(const uint64_t index) { } QCOProgramBuilder::QubitRegister -QCOProgramBuilder::allocQubitRegister(const int64_t size) { +QCOProgramBuilder::allocQubitRegister(const int64_t size, + const StringRef name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } + if (!name.empty() && !qubitRegisterNames.insert(name).second) { + llvm::reportFatalUsageError("Qubit register names must be unique"); + } auto qtensor = qtensorAlloc(size); + if (!name.empty()) { + qtensor.getDefiningOp()->setAttr(QUBIT_REGISTER_NAME_ATTR, + getStringAttr(name)); + } SmallVector qubits; qubits.reserve(size); diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 8c03c7a7f0..8159d40c98 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -15,6 +15,7 @@ #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include "mlir/Support/IRVerification.h" #include "mlir/Support/Passes.h" #include "qc_programs.h" @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +39,7 @@ #include #include #include +#include using namespace mlir; @@ -83,6 +86,68 @@ static LogicalResult runQCOToQCConversion(ModuleOp module) { return pm.run(module); } +TEST(QCOToQCRegressionTest, RetainsQubitRegisterName) { + DialectRegistry registry; + registry.insert(); + MLIRContext context(registry); + context.loadAllAvailableDialects(); + qco::QCOProgramBuilder builder(&context); + builder.initialize(); + std::ignore = builder.allocQubitRegister(2, "named_qubits"); + auto moduleOp = builder.finalize(); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + + memref::AllocOp allocation; + moduleOp->walk([&](memref::AllocOp op) { + if (isa(op.getType().getElementType())) { + allocation = op; + } + }); + ASSERT_TRUE(allocation); + const auto name = + allocation->getAttrOfType(utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + +TEST(QCOToQCRegressionTest, RetainsDynamicQubitRegisterName) { + DialectRegistry registry; + registry.insert(); + MLIRContext context(registry); + context.loadAllAvailableDialects(); + + constexpr llvm::StringLiteral source = R"mlir( +module { + func.func @main(%size: index) attributes {passthrough = ["entry_point"]} { + %reg = qtensor.alloc(%size) {mqt.qubit_register_name = "named_qubits"} : tensor + qtensor.dealloc %reg : tensor + return + } +} +)mlir"; + + auto moduleOp = parseSourceString(source, &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + + memref::AllocOp allocation; + moduleOp->walk([&](memref::AllocOp op) { allocation = op; }); + ASSERT_TRUE(allocation); + EXPECT_TRUE(allocation.getType().isDynamicDim(0)); + ASSERT_EQ(allocation.getDynamicSizes().size(), 1); + EXPECT_EQ(allocation.getDynamicSizes().front(), + allocation->getBlock()->getArgument(0)); + const auto name = + allocation->getAttrOfType(utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + static Value aliasSafeNestedForLoopCtrlOpWithExtractedQubit(qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 635668037e..105bc1ec56 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -18,6 +18,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include "mlir/Dialect/Utils/Utils.h" #include "mlir/Support/IRVerification.h" #include "mlir/Support/Passes.h" #include "qc_programs.h" @@ -54,6 +55,7 @@ #include #include #include +#include using namespace mlir; @@ -585,6 +587,49 @@ module { EXPECT_EQ(deallocations, 1U); } +TEST_F(QCToQCORegressionTest, RetainsQubitRegisterName) { + qc::QCProgramBuilder builder(&context); + builder.initialize(); + std::ignore = builder.allocQubitRegisterStorage(2, "named_qubits"); + auto moduleOp = builder.finalize(); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + + qtensor::AllocOp allocation; + moduleOp->walk([&](qtensor::AllocOp op) { allocation = op; }); + ASSERT_TRUE(allocation); + const auto name = + allocation->getAttrOfType(utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + +TEST_F(QCToQCORegressionTest, RetainsDynamicQubitRegisterName) { + constexpr llvm::StringLiteral source = R"mlir( +module { + func.func @main(%size: index) attributes {passthrough = ["entry_point"]} { + %reg = memref.alloc(%size) {mqt.qubit_register_name = "named_qubits"} : memref + memref.dealloc %reg : memref + return + } +} +)mlir"; + + auto moduleOp = parseSourceString(source, &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + + qtensor::AllocOp allocation; + moduleOp->walk([&](qtensor::AllocOp op) { allocation = op; }); + ASSERT_TRUE(allocation); + EXPECT_TRUE(allocation.getResult().getType().isDynamicDim(0)); + EXPECT_EQ(allocation.getSize(), allocation->getBlock()->getArgument(0)); + const auto name = + allocation->getAttrOfType(utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + TEST_F(QCToQCORegressionTest, RejectsRegisterBackedReferenceEscapes) { constexpr llvm::StringLiteral source = R"mlir( module { diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 6afb521ca1..430ba44956 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -42,6 +42,7 @@ #include #include #include +#include using namespace mlir; using namespace mlir::qc; @@ -128,6 +129,17 @@ TEST_F(QCTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) { "Cannot mix dynamic and static qubit allocation modes"); } +TEST_F(QCTest, BuilderRejectsDuplicateNonEmptyQubitRegisterNames) { + EXPECT_DEATH( + { + QCProgramBuilder builder(context.get()); + builder.initialize(); + std::ignore = builder.allocQubitRegisterStorage(1, "q"); + std::ignore = builder.allocQubitRegisterStorage(1, "q"); + }, + "Qubit register names must be unique"); +} + TEST_F(QCTest, BuilderRejectsOutOfBoundsClassicalRegisterIndices) { EXPECT_DEATH( { diff --git a/mlir/unittests/Dialect/QC/Translation/test_openqasm3_emission.cpp b/mlir/unittests/Dialect/QC/Translation/test_openqasm3_emission.cpp index 0a5ea38028..2af6426a8d 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_openqasm3_emission.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_openqasm3_emission.cpp @@ -527,6 +527,27 @@ module { EXPECT_NE(emitted->find("output bit _mqt_out"), std::string::npos); } +TEST(OpenQASM3EmissionTest, ReusesQubitRegisterNames) { + DialectRegistry registry = emissionDialects(); + MLIRContext context(registry); + context.loadAllAvailableDialects(); + qc::QCProgramBuilder builder(&context); + builder.initialize(); + std::ignore = builder.allocQubitRegister(2, "named_qubits"); + std::ignore = builder.allocQubitRegister(2, "not-valid"); + auto moduleOp = builder.finalize(); + ASSERT_TRUE(moduleOp); + + auto emitted = qc::translateQCToOpenQASM3(*moduleOp); + + ASSERT_TRUE(succeeded(emitted)); + EXPECT_NE(emitted->find("qubit[2] named_qubits;"), std::string::npos); + EXPECT_EQ(emitted->find("qubit[2] not-valid;"), std::string::npos); + EXPECT_TRUE(oq3::frontend::analyzeOpenQASM( + *emitted, {.gatePolicy = oq3::frontend::GatePolicy::Strict})) + << *emitted; +} + TEST(OpenQASM3EmissionTest, DefinesECRWithOneEntanglingGate) { DialectRegistry registry = emissionDialects(); MLIRContext context(registry); diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 9699a23b3d..5f52d68f57 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -1002,6 +1002,26 @@ named_result = measure q; EXPECT_EQ(name.getValue(), "named_result"); } +TEST_F(QASM3TranslationTest, RetainsQubitRegisterName) { + constexpr llvm::StringLiteral source = R"qasm(OPENQASM 3.0; +qubit[2] named_qubits; +)qasm"; + auto translated = qc::translateQASM3ToQC(source, context.get()); + ASSERT_TRUE(translated); + + memref::AllocOp qubitRegister; + translated->walk([&](memref::AllocOp op) { + if (isa(op.getType().getElementType())) { + qubitRegister = op; + } + }); + ASSERT_TRUE(qubitRegister); + const auto name = + qubitRegister->getAttrOfType(utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + TEST_F(QASM3TranslationTest, DistinguishesScalarAndWidthOneQubitAllocations) { constexpr llvm::StringLiteral source = R"qasm(OPENQASM 3.1; qubit scalar; diff --git a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp index f50935e9f2..8ef1bf52d8 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp @@ -180,6 +180,26 @@ TEST_F(QuantumComputationTranslationTest, RetainsClassicalRegisterName) { EXPECT_EQ(name.getValue(), "named_result"); } +TEST_F(QuantumComputationTranslationTest, RetainsQubitRegisterName) { + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "named_qubits"); + + auto translated = mlir::translateQuantumComputationToQC(context.get(), comp); + ASSERT_TRUE(translated); + + mlir::memref::AllocOp qubitRegister; + translated->walk([&](mlir::memref::AllocOp op) { + if (mlir::isa(op.getType().getElementType())) { + qubitRegister = op; + } + }); + ASSERT_TRUE(qubitRegister); + const auto name = qubitRegister->getAttrOfType( + mlir::utils::QUBIT_REGISTER_NAME_ATTR); + ASSERT_TRUE(name); + EXPECT_EQ(name.getValue(), "named_qubits"); +} + TEST_F(QuantumComputationTranslationTest, AllowsSingleBitControlBeforeMeasurement) { ::qc::QuantumComputation comp; diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 960d2e5675..cb13c3cf68 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include using namespace mlir; @@ -133,6 +134,17 @@ TEST_F(QCOTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) { "Cannot mix dynamic and static qubit allocation modes"); } +TEST_F(QCOTest, BuilderRejectsDuplicateNonEmptyQubitRegisterNames) { + EXPECT_DEATH( + { + QCOProgramBuilder builder(context.get()); + builder.initialize(); + std::ignore = builder.allocQubitRegister(1, "q"); + std::ignore = builder.allocQubitRegister(1, "q"); + }, + "Qubit register names must be unique"); +} + TEST_F(QCOTest, BuilderRejectsOutOfBoundsClassicalRegisterIndices) { EXPECT_DEATH( {