From 11d533aba088cd320aa85e09e7b39105173674c8 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 7 Sep 2026 16:16:59 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20Diagnose=20unsupported=20QC?= =?UTF-8?q?=20control=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reject block-successor operations before QC-to-QCO state mapping. The conversion requires structured control flow. Assisted-by: GPT-6 via Codex --- .../mlir/Conversion/QCToQCO/QCToQCO.td | 3 ++ mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 6 ++++ .../Conversion/QCToQCO/CMakeLists.txt | 1 + .../Conversion/QCToQCO/test_qc_to_qco.cpp | 34 +++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td index 6b1c2d8868..16c08981f0 100644 --- a/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td +++ b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td @@ -14,6 +14,9 @@ def QCToQCO : Pass<"qc-to-qco", "mlir::ModuleOp"> { let description = [{ This pass converts all operations from the QC dialect to their equivalent operations in the QCO dialect. It handles the transformation of qubit references in QC to qubit values in QCO, ensuring that the semantics of quantum operations are preserved during the conversion process. + Control flow must use structured SCF operations. Operations with block + successors, including `cf.br`, `cf.cond_br`, and `cf.switch`, are diagnosed + before conversion. }]; let dependentDialects = ["mlir::arith::ArithDialect", "mlir::qco::QCODialect", diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 322b9ac642..8c0abd8352 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -465,6 +465,12 @@ static void commitQubits(LoweringState& state, Operation* anchor, [[nodiscard]] static LogicalResult validateQuantumValueSources(Operation* root) { const auto result = root->walk([&](Operation* operation) { + if (operation->getNumSuccessors() != 0) { + operation->emitOpError( + "QC-to-QCO does not support unstructured control flow; use SCF " + "operations"); + return WalkResult::interrupt(); + } if (auto returnOp = dyn_cast(operation)) { auto function = returnOp->getParentOfType(); llvm::SmallDenseSet returnedQubits; diff --git a/mlir/unittests/Conversion/QCToQCO/CMakeLists.txt b/mlir/unittests/Conversion/QCToQCO/CMakeLists.txt index 1bcb89dc62..0d783be5e9 100644 --- a/mlir/unittests/Conversion/QCToQCO/CMakeLists.txt +++ b/mlir/unittests/Conversion/QCToQCO/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(${target_name} test_qc_to_qco.cpp) target_link_libraries( ${target_name} PRIVATE MLIRParser + MLIRControlFlowDialect MLIRSupportMQT GTest::gtest_main MLIRQCProgramBuilder diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 4803de99c7..0681e2d68a 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -808,6 +809,39 @@ module { EXPECT_TRUE(isa(*call.getResult(1).getUsers().begin())); } +TEST_F(QCToQCORegressionTest, RejectsUnstructuredControlFlow) { + context.getOrLoadDialect(); + auto moduleOp = parseSourceString(R"mlir( +module { + func.func @main() attributes {mqt.entry_point} { + %q = qc.static 0 : !qc.qubit + %c = arith.constant true + cf.cond_br %c, ^then, ^else + ^then: + qc.x %q : !qc.qubit + return + ^else: + qc.z %q : !qc.qubit + return + } +} +)mlir", + &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + + bool sawExpectedDiagnostic = false; + ScopedDiagnosticHandler handler(&context, [&](Diagnostic& diagnostic) { + sawExpectedDiagnostic |= + StringRef(diagnostic.str()) + .contains("QC-to-QCO does not support unstructured control flow"); + return success(); + }); + + EXPECT_TRUE(failed(runQCToQCOConversion(*moduleOp))); + EXPECT_TRUE(sawExpectedDiagnostic); +} + TEST_F(QCToQCORegressionTest, PreflightRejectsNonOneDimensionalQubitRegisters) { constexpr llvm::StringLiteral source = R"mlir( module { From 63cd68c33b6bc0fd20332bf870ea694100df9d9a Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 7 Sep 2026 16:39:09 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=87=20Preserve=20the=20GoogleTest?= =?UTF-8?q?=20setup=20override=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-6 via Codex --- mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 0681e2d68a..5601030f02 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -95,6 +95,8 @@ class QCToQCOTest : public testing::TestWithParam { protected: std::unique_ptr context; + // GoogleTest requires this override name. + // NOLINTNEXTLINE(readability-identifier-naming) void SetUp() override { // Register all necessary dialects DialectRegistry registry; From b2d71e524a2f2cb78cd3a5982d5c8dbfeb1c9820 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 7 Sep 2026 18:31:58 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=87=20Remove=20redundant=20SetUp?= =?UTF-8?q?=20naming=20suppression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-6 via Codex --- mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 5601030f02..0681e2d68a 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -95,8 +95,6 @@ class QCToQCOTest : public testing::TestWithParam { protected: std::unique_ptr context; - // GoogleTest requires this override name. - // NOLINTNEXTLINE(readability-identifier-naming) void SetUp() override { // Register all necessary dialects DialectRegistry registry; From fcd8fc70ba3a27a1aae8bef620952d30e13a9418 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 7 Sep 2026 21:00:40 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Clean=20up=20QC-to-QCO?= =?UTF-8?q?=20input=20rejection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the early rejection of operations with block successors. Remove the unreachable branch conversion pattern and its unused include, and name the preflight check for its supported-input contract. Assisted-by: GPT-6 via Codex --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 8c0abd8352..91a06b0911 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -461,9 +460,8 @@ static void commitQubits(LoweringState& state, Operation* anchor, return success(); } -/// Rejects quantum SSA sources unsupported by the lowering state. -[[nodiscard]] static LogicalResult -validateQuantumValueSources(Operation* root) { +/// Rejects input unsupported by the lowering state. +[[nodiscard]] static LogicalResult validateSupportedInput(Operation* root) { const auto result = root->walk([&](Operation* operation) { if (operation->getNumSuccessors() != 0) { operation->emitOpError( @@ -2026,7 +2024,7 @@ struct QCToQCO final : impl::QCToQCOBase { LoweringState preflightState; if (failed(validateModifierBodies(moduleOp)) || - failed(validateQuantumValueSources(moduleOp)) || + failed(validateSupportedInput(moduleOp)) || failed(collectRegisterAccesses(moduleOp, preflightState))) { signalPassFailure(); return; @@ -2134,9 +2132,6 @@ struct QCToQCO final : impl::QCToQCOBase { target.addDynamicallyLegalOp( [&](func::CallOp op) { return typeConverter.isLegal(op); }); - // Conversion of qc types in control-flow ops (e.g., cf.br, cf.cond_br) - populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); - // Convert structured parents and their contents first. if (failed(applyPartialConversion(moduleOp, target, std::move(patterns)))) { signalPassFailure();