From 2c647551d1c819e0f92a5599ceb67f7543711e27 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 30 Aug 2026 12:38:26 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Constrain=20MLIR=20con?= =?UTF-8?q?versions=20to=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare module-wide conversion passes on ModuleOp and use the typed pass root in their implementations. This prevents unsafe nested invocation and removes redundant runtime casts. Assisted-by: Codex --- .../Conversion/CBitToMemRef/CBitToMemRef.td | 2 +- .../mlir/Conversion/JeffToQCO/JeffToQCO.td | 2 +- .../mlir/Conversion/QCOToJeff/QCOToJeff.td | 2 +- .../mlir/Conversion/QCOToQC/QCOToQC.td | 2 +- .../QCToQIR/QIRAdaptive/QCToQIRAdaptive.td | 2 +- .../Conversion/QCToQIR/QIRBase/QCToQIRBase.td | 2 +- .../Conversion/CBitToMemRef/CBitToMemRef.cpp | 2 +- mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 49 +++++++------------ mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp | 44 ++++++++--------- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 4 +- .../QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp | 4 +- .../QCToQIR/QIRBase/QCToQIRBase.cpp | 4 +- 12 files changed, 51 insertions(+), 68 deletions(-) diff --git a/mlir/include/mlir/Conversion/CBitToMemRef/CBitToMemRef.td b/mlir/include/mlir/Conversion/CBitToMemRef/CBitToMemRef.td index e71013d0f0..c03deb739e 100644 --- a/mlir/include/mlir/Conversion/CBitToMemRef/CBitToMemRef.td +++ b/mlir/include/mlir/Conversion/CBitToMemRef/CBitToMemRef.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def ConvertCBitToMemRef : Pass<"convert-cbit-to-memref"> { +def ConvertCBitToMemRef : Pass<"convert-cbit-to-memref", "mlir::ModuleOp"> { let summary = "Lower CBit registers to memrefs"; let description = [{ diff --git a/mlir/include/mlir/Conversion/JeffToQCO/JeffToQCO.td b/mlir/include/mlir/Conversion/JeffToQCO/JeffToQCO.td index f2c5df2fcd..b5143e0f6e 100644 --- a/mlir/include/mlir/Conversion/JeffToQCO/JeffToQCO.td +++ b/mlir/include/mlir/Conversion/JeffToQCO/JeffToQCO.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def JeffToQCO : Pass<"jeff-to-qco"> { +def JeffToQCO : Pass<"jeff-to-qco", "mlir::ModuleOp"> { let summary = "Convert `jeff` operations to QCO operations"; let description = [{ diff --git a/mlir/include/mlir/Conversion/QCOToJeff/QCOToJeff.td b/mlir/include/mlir/Conversion/QCOToJeff/QCOToJeff.td index d057b31a57..58ea959982 100644 --- a/mlir/include/mlir/Conversion/QCOToJeff/QCOToJeff.td +++ b/mlir/include/mlir/Conversion/QCOToJeff/QCOToJeff.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def QCOToJeff : Pass<"qco-to-jeff"> { +def QCOToJeff : Pass<"qco-to-jeff", "mlir::ModuleOp"> { let summary = "Convert QCO operations to `jeff` operations"; let description = [{ diff --git a/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td index e77e57ad9a..18fdf68fa7 100644 --- a/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td +++ b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def QCOToQC : Pass<"qco-to-qc"> { +def QCOToQC : Pass<"qco-to-qc", "mlir::ModuleOp"> { let summary = "Convert QCO dialect to QC dialect."; let description = [{ diff --git a/mlir/include/mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.td b/mlir/include/mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.td index a56d0b263e..7bc2a0079f 100644 --- a/mlir/include/mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.td +++ b/mlir/include/mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def QCToQIRAdaptive : Pass<"qc-to-qir-adaptive"> { +def QCToQIRAdaptive : Pass<"qc-to-qir-adaptive", "mlir::ModuleOp"> { let summary = "Lower the QC dialect to the LLVM dialect compliant with the " "QIR Adaptive Profile 2.1"; diff --git a/mlir/include/mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.td b/mlir/include/mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.td index 6d15fbb842..041dc983c3 100644 --- a/mlir/include/mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.td +++ b/mlir/include/mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.td @@ -8,7 +8,7 @@ include "mlir/Pass/PassBase.td" -def QCToQIRBase : Pass<"qc-to-qir-base"> { +def QCToQIRBase : Pass<"qc-to-qir-base", "mlir::ModuleOp"> { let summary = "Lower the QC dialect to the LLVM dialect compliant with the " "QIR Base Profile 2.1"; diff --git a/mlir/lib/Conversion/CBitToMemRef/CBitToMemRef.cpp b/mlir/lib/Conversion/CBitToMemRef/CBitToMemRef.cpp index a9921cc0d5..0f7854e830 100644 --- a/mlir/lib/Conversion/CBitToMemRef/CBitToMemRef.cpp +++ b/mlir/lib/Conversion/CBitToMemRef/CBitToMemRef.cpp @@ -103,7 +103,7 @@ struct ConvertCBitToMemRef final protected: void runOnOperation() override { MLIRContext* context = &getContext(); - auto* moduleOp = getOperation(); + auto moduleOp = getOperation(); CBitTypeConverter typeConverter; ConversionTarget target(*context); RewritePatternSet patterns(context); diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index cff792c0d6..6fb9f44099 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -204,20 +204,15 @@ static void createBarrierOp(jeff::CustomOp& op, jeff::CustomOpAdaptor& adaptor, /** * @brief Gets the name of the entry point from the module attributes */ -static StringRef getEntryPointName(Operation* op) { - auto module = dyn_cast(op); - if (!module) { - llvm::reportFatalInternalError("Expected a module operation"); - } - - auto entryPointAttr = module->getAttr("jeff.entrypoint"); +static StringRef getEntryPointName(ModuleOp moduleOp) { + auto entryPointAttr = moduleOp->getAttr("jeff.entrypoint"); if (!entryPointAttr) { llvm::reportFatalInternalError( "Module is missing 'jeff.entrypoint' attribute"); } auto entryPoint = cast(entryPointAttr).getUInt(); - auto stringsAttr = module->getAttr("jeff.strings"); + auto stringsAttr = moduleOp->getAttr("jeff.strings"); if (!stringsAttr) { llvm::reportFatalInternalError( "Module is missing 'jeff.strings' attribute"); @@ -234,25 +229,17 @@ static StringRef getEntryPointName(Operation* op) { /** * @brief Cleans up the module after conversion * - * @param op The module operation to clean up - * @return LogicalResult Success or failure of the cleanup + * @param moduleOp The module operation to clean up */ -static LogicalResult cleanUp(Operation* op) { - auto module = dyn_cast(op); - if (!module) { - return failure(); - } - - // Remove module attributes - module->removeAttr("jeff.entrypoint"); - module->removeAttr("jeff.strings"); - module->removeAttr("jeff.tool"); - module->removeAttr("jeff.toolVersion"); - module->removeAttr("jeff.version"); - module->removeAttr("jeff.versionMinor"); - module->removeAttr("jeff.versionPatch"); - - return success(); +static void cleanUp(ModuleOp moduleOp) { + /// Remove module attributes. + moduleOp->removeAttr("jeff.entrypoint"); + moduleOp->removeAttr("jeff.strings"); + moduleOp->removeAttr("jeff.tool"); + moduleOp->removeAttr("jeff.toolVersion"); + moduleOp->removeAttr("jeff.version"); + moduleOp->removeAttr("jeff.versionMinor"); + moduleOp->removeAttr("jeff.versionPatch"); } /** @@ -1288,7 +1275,7 @@ struct JeffToQCO final : impl::JeffToQCOBase { protected: void runOnOperation() override { MLIRContext* context = &getContext(); - auto* module = getOperation(); + auto moduleOp = getOperation(); ConversionTarget target(*context); RewritePatternSet patterns(context); @@ -1302,7 +1289,7 @@ struct JeffToQCO final : impl::JeffToQCOBase { tensor::TensorDialect, scf::SCFDialect>(); target.addDynamicallyLegalOp([&](func::FuncOp op) { - return (op.getSymName() != getEntryPointName(module) || + return (op.getSymName() != getEntryPointName(moduleOp) || mqt::isEntryPoint(op)) && typeConverter.isSignatureLegal(op.getFunctionType()) && typeConverter.isLegal(&op.getBody()); @@ -1343,14 +1330,12 @@ struct JeffToQCO final : impl::JeffToQCOBase { context); // Apply the conversion - if (applyPartialConversion(module, target, std::move(patterns)).failed()) { + if (applyPartialConversion(moduleOp, target, std::move(patterns)).failed()) { signalPassFailure(); return; } - if (cleanUp(module).failed()) { - signalPassFailure(); - } + cleanUp(moduleOp); } }; diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index 1af9a9b33e..a0857c50e5 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -436,11 +436,11 @@ static void createPPROp(QCOOpType& op, ConversionPatternRewriter& rewriter, } /** - * @brief Updates all `jeff.yield` operations in @p module to use the latest + * @brief Updates all `jeff.yield` operations in @p moduleOp to use the latest * classical-bit-register array values. */ -static void patchCregYields(Operation* module, LoweringState& state) { - module->walk([&](jeff::YieldOp yieldOp) { +static void patchCregYields(ModuleOp moduleOp, LoweringState& state) { + moduleOp->walk([&](jeff::YieldOp yieldOp) { auto* values = state.cbitState.getRegionValues(yieldOp->getParentRegion()); if (values == nullptr) { return; @@ -460,21 +460,16 @@ static void patchCregYields(Operation* module, LoweringState& state) { /** * @brief Cleans up the module after conversion * - * @param op The module operation to clean up + * @param moduleOp The module operation to clean up * @param state The lowering state * @return LogicalResult Success or failure of the cleanup */ -static LogicalResult cleanUp(Operation* op, LoweringState& state) { +static LogicalResult cleanUp(ModuleOp moduleOp, LoweringState& state) { if (state.entryPointName.empty()) { return failure(); } - auto module = dyn_cast(op); - if (!module) { - return failure(); - } - - for (auto funcOp : module.getOps()) { + for (auto funcOp : moduleOp.getOps()) { state.strings.emplace_back(funcOp.getSymName()); } @@ -488,26 +483,29 @@ static LogicalResult cleanUp(Operation* op, LoweringState& state) { } const auto entryPoint = static_cast(distance); - // Set module attributes - OpBuilder builder(module.getContext()); + /// Set module attributes. + OpBuilder builder(moduleOp.getContext()); auto uint16Type = builder.getIntegerType(16, false); - module->setAttr("jeff.entrypoint", - builder.getIntegerAttr(uint16Type, entryPoint)); + moduleOp->setAttr("jeff.entrypoint", + builder.getIntegerAttr(uint16Type, entryPoint)); SmallVector stringRefs; stringRefs.reserve(state.strings.size()); for (const auto& str : state.strings) { stringRefs.emplace_back(str); } - module->setAttr("jeff.strings", builder.getStrArrayAttr(stringRefs)); + moduleOp->setAttr("jeff.strings", builder.getStrArrayAttr(stringRefs)); - module->setAttr("jeff.tool", builder.getStringAttr("mqt-cc")); - module->setAttr("jeff.toolVersion", builder.getStringAttr(MQT_CORE_VERSION)); + moduleOp->setAttr("jeff.tool", builder.getStringAttr("mqt-cc")); + moduleOp->setAttr("jeff.toolVersion", + builder.getStringAttr(MQT_CORE_VERSION)); - module->setAttr("jeff.version", builder.getIntegerAttr(uint16Type, 0)); - module->setAttr("jeff.versionMinor", builder.getIntegerAttr(uint16Type, 3)); - module->setAttr("jeff.versionPatch", builder.getIntegerAttr(uint16Type, 0)); + moduleOp->setAttr("jeff.version", builder.getIntegerAttr(uint16Type, 0)); + moduleOp->setAttr("jeff.versionMinor", + builder.getIntegerAttr(uint16Type, 3)); + moduleOp->setAttr("jeff.versionPatch", + builder.getIntegerAttr(uint16Type, 0)); return success(); } @@ -1854,8 +1852,8 @@ struct QCOToJeff final : impl::QCOToJeffBase { protected: void runOnOperation() override { MLIRContext* context = &getContext(); - auto* moduleOp = getOperation(); - if (failed(mqt::normalizeGlobalPhases(cast(moduleOp)))) { + auto moduleOp = getOperation(); + if (failed(mqt::normalizeGlobalPhases(moduleOp))) { signalPassFailure(); return; } diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index b3f8d9c6ca..9d96069e38 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -1174,7 +1174,7 @@ struct QCOToQC final : impl::QCOToQCBase { protected: void runOnOperation() override { MLIRContext* context = &getContext(); - auto* module = getOperation(); + auto moduleOp = getOperation(); // Create state object to track the qubit addressing mode LoweringState state; @@ -1251,7 +1251,7 @@ struct QCOToQC final : impl::QCOToQCBase { populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); // Apply the conversion - if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + if (failed(applyPartialConversion(moduleOp, target, std::move(patterns)))) { signalPassFailure(); } } diff --git a/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp b/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp index eb614ba200..c2953e5955 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp @@ -684,8 +684,8 @@ struct QCToQIRAdaptive final : impl::QCToQIRAdaptiveBase { */ void runOnOperation() override { MLIRContext* ctx = &getContext(); - auto* moduleOp = getOperation(); - if (failed(mqt::normalizeGlobalPhases(cast(moduleOp)))) { + auto moduleOp = getOperation(); + if (failed(mqt::normalizeGlobalPhases(moduleOp))) { signalPassFailure(); return; } diff --git a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp index a8f54ccc75..d402c18c31 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp @@ -461,8 +461,8 @@ struct QCToQIRBase final : impl::QCToQIRBaseBase { */ void runOnOperation() override { MLIRContext* ctx = &getContext(); - auto* moduleOp = getOperation(); - if (failed(mqt::normalizeGlobalPhases(cast(moduleOp)))) { + auto moduleOp = getOperation(); + if (failed(mqt::normalizeGlobalPhases(moduleOp))) { signalPassFailure(); return; } From 21f18c8279562935bef195646c1434b72d4389d9 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 30 Aug 2026 12:41:56 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20Reject=20malformed=20jeff=20?= =?UTF-8?q?module=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate the jeff entry-point attributes before dialect conversion and report invalid metadata through the pass diagnostic instead of terminating the process. Assisted-by: Codex --- mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 40 +++++++------ .../JeffRoundTrip/test_jeff_round_trip.cpp | 60 +++++++++++++++++-- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index 6fb9f44099..e839a929bb 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -204,26 +203,28 @@ static void createBarrierOp(jeff::CustomOp& op, jeff::CustomOpAdaptor& adaptor, /** * @brief Gets the name of the entry point from the module attributes */ -static StringRef getEntryPointName(ModuleOp moduleOp) { - auto entryPointAttr = moduleOp->getAttr("jeff.entrypoint"); - if (!entryPointAttr) { - llvm::reportFatalInternalError( - "Module is missing 'jeff.entrypoint' attribute"); +static FailureOr getEntryPointName(ModuleOp moduleOp) { + auto entryPointAttr = moduleOp->getAttrOfType("jeff.entrypoint"); + if (!entryPointAttr || !entryPointAttr.getType().isUnsignedInteger()) { + return moduleOp.emitError( + "requires an unsigned integer 'jeff.entrypoint' attribute"); } - auto entryPoint = cast(entryPointAttr).getUInt(); + auto entryPoint = entryPointAttr.getUInt(); - auto stringsAttr = moduleOp->getAttr("jeff.strings"); + auto stringsAttr = moduleOp->getAttrOfType("jeff.strings"); if (!stringsAttr) { - llvm::reportFatalInternalError( - "Module is missing 'jeff.strings' attribute"); + return moduleOp.emitError("requires an array 'jeff.strings' attribute"); } - auto strings = cast(stringsAttr); - if (entryPoint >= strings.size()) { - llvm::reportFatalInternalError("Entry point index is out of bounds"); + if (entryPoint >= stringsAttr.size()) { + return moduleOp.emitError("'jeff.entrypoint' index is out of bounds"); } - return cast(strings[entryPoint]).getValue(); + auto name = dyn_cast(stringsAttr[entryPoint]); + if (!name) { + return moduleOp.emitError("'jeff.entrypoint' must index a string"); + } + return name.getValue(); } /** @@ -1276,6 +1277,11 @@ struct JeffToQCO final : impl::JeffToQCOBase { void runOnOperation() override { MLIRContext* context = &getContext(); auto moduleOp = getOperation(); + auto entryPointName = getEntryPointName(moduleOp); + if (failed(entryPointName)) { + signalPassFailure(); + return; + } ConversionTarget target(*context); RewritePatternSet patterns(context); @@ -1289,8 +1295,7 @@ struct JeffToQCO final : impl::JeffToQCOBase { tensor::TensorDialect, scf::SCFDialect>(); target.addDynamicallyLegalOp([&](func::FuncOp op) { - return (op.getSymName() != getEntryPointName(moduleOp) || - mqt::isEntryPoint(op)) && + return (op.getSymName() != *entryPointName || mqt::isEntryPoint(op)) && typeConverter.isSignatureLegal(op.getFunctionType()) && typeConverter.isLegal(&op.getBody()); }); @@ -1330,7 +1335,8 @@ struct JeffToQCO final : impl::JeffToQCOBase { context); // Apply the conversion - if (applyPartialConversion(moduleOp, target, std::move(patterns)).failed()) { + if (applyPartialConversion(moduleOp, target, std::move(patterns)) + .failed()) { signalPassFailure(); return; } diff --git a/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp b/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp index a9528d3759..6d0dd770c8 100644 --- a/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp +++ b/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp @@ -354,17 +354,65 @@ static Value nestedWhileOpIfOp(qco::QCOProgramBuilder& b) { return b.measure(res[0]).second; } -static LogicalResult convertQCOToJeff(ModuleOp module) { - PassManager pm(module.getContext()); +static LogicalResult convertQCOToJeff(ModuleOp moduleOp) { + PassManager pm(moduleOp.getContext()); pm.addPass(mlir::mqt::createUnrollModifiers()); pm.addPass(createQCOToJeff()); - return pm.run(module); + return pm.run(moduleOp); } -static LogicalResult convertJeffToQCO(ModuleOp module) { - PassManager pm(module.getContext()); +static LogicalResult convertJeffToQCO(ModuleOp moduleOp) { + PassManager pm(moduleOp.getContext()); pm.addPass(createJeffToQCO()); - return pm.run(module); + return pm.run(moduleOp); +} + +TEST(JeffRoundTripRegressionTest, RejectsInvalidJeffModuleMetadata) { + DialectRegistry registry; + registry.insert(); + MLIRContext context(registry); + context.loadAllAvailableDialects(); + OpBuilder builder(&context); + + const auto rejects = [&](const ArrayRef attributes, + const StringRef expected) { + auto moduleOp = ModuleOp::create(builder.getUnknownLoc()); + moduleOp->setAttrs(builder.getDictionaryAttr(attributes)); + bool sawExpectedDiagnostic = false; + ScopedDiagnosticHandler handler(&context, [&](Diagnostic& diagnostic) { + std::string message; + llvm::raw_string_ostream stream(message); + diagnostic.print(stream); + sawExpectedDiagnostic |= StringRef(message).contains(expected); + return success(); + }); + EXPECT_TRUE(failed(convertJeffToQCO(moduleOp))); + EXPECT_TRUE(sawExpectedDiagnostic); + }; + + const auto uint16Type = builder.getIntegerType(16, false); + const auto entryPoint = builder.getNamedAttr( + "jeff.entrypoint", builder.getIntegerAttr(uint16Type, 0)); + const auto strings = + builder.getNamedAttr("jeff.strings", builder.getStrArrayAttr({"main"})); + rejects({}, "requires an unsigned integer 'jeff.entrypoint' attribute"); + rejects( + {builder.getNamedAttr("jeff.entrypoint", builder.getStringAttr("main"))}, + "requires an unsigned integer 'jeff.entrypoint' attribute"); + rejects( + {builder.getNamedAttr("jeff.entrypoint", builder.getI16IntegerAttr(0))}, + "requires an unsigned integer 'jeff.entrypoint' attribute"); + rejects({entryPoint}, "requires an array 'jeff.strings' attribute"); + rejects({builder.getNamedAttr("jeff.entrypoint", + builder.getIntegerAttr(uint16Type, 1)), + strings}, + "'jeff.entrypoint' index is out of bounds"); + rejects( + {entryPoint, builder.getNamedAttr( + "jeff.strings", + builder.getArrayAttr({builder.getI32IntegerAttr(0)}))}, + "'jeff.entrypoint' must index a string"); } TEST(JeffRoundTripRegressionTest, RestoresStatusResultAtEndOfEntryPoint) { From 9a1a21adbffe92b00f8658ac9ab7858e4bb11f2b Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 30 Aug 2026 12:45:25 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20Reject=20multi-block=20QIR?= =?UTF-8?q?=20Base=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check the QC entry function before normalization and type conversion. Unsupported multi-block input now fails through the pass diagnostic without mutating the module or terminating the process. Assisted-by: Codex --- .../QCToQIR/QIRBase/QCToQIRBase.cpp | 14 ++++--- .../QCToQIRBase/test_qc_to_qir_base.cpp | 37 +++++++++++++++++-- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp index d402c18c31..641018356e 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp @@ -13,12 +13,12 @@ #include "mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.h" #include "mlir/Dialect/CBit/IR/CBitDialect.h" #include "mlir/Dialect/CBit/IR/CBitOps.h" +#include "mlir/Dialect/MQT/IR/MQTDialect.h" #include "mlir/Dialect/MQT/Transforms/GlobalPhaseNormalization.h" #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QIR/Utils/QIRUtils.h" -#include #include #include #include @@ -380,11 +380,6 @@ struct QCToQIRBase final : impl::QCToQIRBaseBase { * @param main The main LLVM function to restructure */ static void ensureBlocks(LLVM::LLVMFuncOp& main, LoweringState& state) { - if (main.getBlocks().size() > 1) { - llvm::reportFatalInternalError( - "Modules with multiple blocks are not supported in the Base Profile"); - } - // Get the existing block auto* bodyBlock = &main.front(); OpBuilder builder(main.getBody()); @@ -462,6 +457,13 @@ struct QCToQIRBase final : impl::QCToQIRBaseBase { void runOnOperation() override { MLIRContext* ctx = &getContext(); auto moduleOp = getOperation(); + auto entryPoint = mqt::getEntryPoint(moduleOp); + if (entryPoint && !entryPoint.getBody().hasOneBlock()) { + entryPoint.emitError( + "QIR Base Profile requires a single-block entry function"); + signalPassFailure(); + return; + } if (failed(mqt::normalizeGlobalPhases(moduleOp))) { signalPassFailure(); return; diff --git a/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp b/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp index e2a2326e1c..41bfced7c1 100644 --- a/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp +++ b/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp @@ -84,11 +84,11 @@ class QCToQIRBaseTest : public testing::TestWithParam { } // namespace -static LogicalResult runQCToQIRBaseConversion(ModuleOp module) { - PassManager pm(module.getContext()); +static LogicalResult runQCToQIRBaseConversion(ModuleOp moduleOp) { + PassManager pm(moduleOp.getContext()); pm.addPass(mlir::mqt::createUnrollModifiers()); pm.addPass(createQCToQIRBase()); - return pm.run(module); + return pm.run(moduleOp); } static void expectFollowingXIsUncontrolled( @@ -126,6 +126,37 @@ TEST(QCToQIRBaseNativeTest, EmptyCtrlDoesNotControlFollowingGate) { }); } +TEST(QCToQIRBaseNativeTest, RejectsMultiBlockEntryFunctionWithoutMutation) { + MLIRContext context; + context.loadDialect(); + qc::QCProgramBuilder builder(&context); + builder.initialize(); + auto moduleOp = builder.finalize(); + ASSERT_TRUE(moduleOp); + auto entryPoint = moduleOp->lookupSymbol("main"); + ASSERT_TRUE(entryPoint); + auto* extraBlock = &entryPoint.getBody().emplaceBlock(); + builder.setInsertionPointToEnd(extraBlock); + auto status = + arith::ConstantIntOp::create(builder, builder.getUnknownLoc(), 0, 64); + func::ReturnOp::create(builder, builder.getUnknownLoc(), status.getResult()); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + + bool sawExpectedDiagnostic = false; + ScopedDiagnosticHandler handler(&context, [&](Diagnostic& diagnostic) { + std::string message; + llvm::raw_string_ostream stream(message); + diagnostic.print(stream); + sawExpectedDiagnostic |= StringRef(message).contains( + "QIR Base Profile requires a single-block entry function"); + return success(); + }); + EXPECT_TRUE(failed(runQCToQIRBaseConversion(*moduleOp))); + EXPECT_TRUE(sawExpectedDiagnostic); + EXPECT_EQ(entryPoint.getBlocks().size(), 2); +} + TEST(QCToQIRBaseNativeTest, ControlledBarrierDoesNotControlFollowingGate) { expectFollowingXIsUncontrolled( [](qc::QCProgramBuilder& builder, Value control, Value target) { From 7ac0d2b59ecbeb85d1aa18de4245704d3aad9a43 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 30 Aug 2026 12:46:43 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Inline=20jeff=20metada?= =?UTF-8?q?ta=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the one-call cleanup wrapper now that the module pass contract makes its failure path impossible. Assisted-by: Codex --- mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 24 ++++++--------------- mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp | 7 ++---- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index e839a929bb..eb983470fc 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -227,22 +227,6 @@ static FailureOr getEntryPointName(ModuleOp moduleOp) { return name.getValue(); } -/** - * @brief Cleans up the module after conversion - * - * @param moduleOp The module operation to clean up - */ -static void cleanUp(ModuleOp moduleOp) { - /// Remove module attributes. - moduleOp->removeAttr("jeff.entrypoint"); - moduleOp->removeAttr("jeff.strings"); - moduleOp->removeAttr("jeff.tool"); - moduleOp->removeAttr("jeff.toolVersion"); - moduleOp->removeAttr("jeff.version"); - moduleOp->removeAttr("jeff.versionMinor"); - moduleOp->removeAttr("jeff.versionPatch"); -} - /** * @brief Checks if a type is a linear type */ @@ -1341,7 +1325,13 @@ struct JeffToQCO final : impl::JeffToQCOBase { return; } - cleanUp(moduleOp); + moduleOp->removeAttr("jeff.entrypoint"); + moduleOp->removeAttr("jeff.strings"); + moduleOp->removeAttr("jeff.tool"); + moduleOp->removeAttr("jeff.toolVersion"); + moduleOp->removeAttr("jeff.version"); + moduleOp->removeAttr("jeff.versionMinor"); + moduleOp->removeAttr("jeff.versionPatch"); } }; diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index a0857c50e5..505c54cd5c 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -483,7 +483,6 @@ static LogicalResult cleanUp(ModuleOp moduleOp, LoweringState& state) { } const auto entryPoint = static_cast(distance); - /// Set module attributes. OpBuilder builder(moduleOp.getContext()); auto uint16Type = builder.getIntegerType(16, false); @@ -502,10 +501,8 @@ static LogicalResult cleanUp(ModuleOp moduleOp, LoweringState& state) { builder.getStringAttr(MQT_CORE_VERSION)); moduleOp->setAttr("jeff.version", builder.getIntegerAttr(uint16Type, 0)); - moduleOp->setAttr("jeff.versionMinor", - builder.getIntegerAttr(uint16Type, 3)); - moduleOp->setAttr("jeff.versionPatch", - builder.getIntegerAttr(uint16Type, 0)); + moduleOp->setAttr("jeff.versionMinor", builder.getIntegerAttr(uint16Type, 3)); + moduleOp->setAttr("jeff.versionPatch", builder.getIntegerAttr(uint16Type, 0)); return success(); }