Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .agent/plans/qir-reusable-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Flatten reusable quantum functions for QIR output

This ExecPlan follows `.agent/PLANS.md`. Keep its progress, discoveries,
decisions, and outcome current. Run commands from the repository root.

## Purpose

QIR is a flat output format in the compiler: its lowering does not preserve QC
or QCO function calls. Make Base and Adaptive QIR generation accept reusable
unitary helpers without changing the compact QC/QCO representation used by other
outputs. Calls nested in quantum modifiers must retain their phase and modifier
semantics.

## Progress

- [x] (2026-09-04) Rebase onto current `main` and the finalized jeff function
layer, adapting the implementation to the split Programs/Pipeline
libraries.
- [x] (2026-09-04) Reuse MLIR's inliner and existing phase, modifier, and
canonicalization passes for flat QIR preparation.
- [x] (2026-09-04) Register Func and LLVM inliner extensions for compiler-owned
and caller-owned contexts.
- [x] (2026-09-04) Keep early QCO inlining exclusive to coordinated QIR output.
- [x] (2026-09-04) Preserve direct, CLI, jeff-imported, and both-profile tests.
- [x] (2026-09-04) Apply the independent QIR/MLIR specialist review: remove a
duplicate textual-inliner check and defer an inert OpenQASM test switch.
- [x] (2026-09-04) Build the compiler and CLI and pass all 161 compiler tests.
- [ ] Run repository lint on the final stack.

## Discoveries

Plain reusable calls are illegal at the QC-to-QIR boundary. A helper containing
a global phase under an integral power needs more than inlining: phase
normalization extracts and scales the phase, modifier unrolling distributes the
supported operations, and canonicalization folds the remaining one-operation
modifier.

A same-wire composite helper under a power must be exposed while it is still QCO
so existing QCO synthesis can reduce it safely. Distributing a power over a
general noncommuting sequence would be incorrect. Early QCO inlining therefore
belongs only to QIR-bound coordinated pipelines, before target compilation or
the default QCO optimization pipeline.

MLIR's stock inliner depends on promised Func and LLVM interfaces. Compiler
contexts and contexts adopted by typed programs must install those extensions;
otherwise the public textual `inline` pipeline can abort before examining QC or
QCO operations.

Current `main` separates context/parsing code in `Programs.cpp` from conversion
and coordinated compilation in `Pipeline.cpp`. Inliner-extension registration
stays with context ownership; QIR preparation and output routing stay in the
pipeline library.

## Decisions

Use `createInlinerPass`, `NormalizeGlobalPhases`, `UnrollModifiers`, and the
canonicalizer. Do not add a QIR-specific call lowering, custom call graph, or
new synthesis pass.

Install standard inliner extensions in `createCompilerContext` and when a typed
program adopts a caller-owned context. This makes the public compiler program
contract independent of who constructed the context.

Inline all QCO calls early only for Base or Adaptive QIR output. Preserve calls
for QCO, QC, OpenQASM, and jeff output. The direct `QCProgram::intoQIR` path
uses the common QC preparation pipeline immediately before profile lowering.

Keep unsupported composite powers fail-closed. The compiler need not invent an
unsafe algebraic rewrite merely to accept a hypothetical program.

## Scope

`mlir/lib/Compiler/Programs.cpp` owns compiler-context extension registration.
`mlir/lib/Compiler/Pipeline.cpp` owns typed and coordinated QIR preparation.
`mlir/lib/Support/Passes.cpp` exposes the shared QC-to-QIR preparation sequence.
`mlir/tools/mqt-cc/mqt-cc.cpp` mirrors the same boundaries for its direct MLIR
pipeline.

The compiler regression covers a phase-bearing unitary helper under nested power
and control, caller-owned textual inlining, early QCO exposure, and a
binary-restored jeff helper through both QIR profiles. The existing OpenQASM
program matrix continues to exercise flat QIR generation from production
frontend inputs.

## Validation

Configure and build the compiler and CLI:

cmake --preset release
cmake --build --preset release --target mqt-core-mlir-unittests-compiler mqt-cc -j4

Run the reusable-function regressions and full compiler test binary:

build/release/mlir/unittests/Compiler/mqt-core-mlir-unittests-compiler --gtest_filter='CompilerPipelineTest.*QIR*:*OpenQASM*'
build/release/mlir/unittests/Compiler/mqt-core-mlir-unittests-compiler

The nested helper must produce Base and Adaptive QIR with one correctly scaled
relative-phase call. Caller-owned contexts must run the textual inliner without
an abort. Coordinated QIR output must expose QCO helpers before synthesis, while
non-QIR outputs retain them.

Finish once on the final stack with:

uvx nox -s cpp-lint
uvx nox -s lint

Hosted CI is separate evidence and counts only after the final rewritten branch
is pushed. Builds, tests, and extension registration are repeatable. This plan
does not authorize unrelated remote changes.

## Outcome

Focused validation passes; final stack lint remains. The rebased implementation
uses native MLIR infrastructure and adds no call-specific QIR representation or
analysis. An independent specialist found the production implementation
idiomatic and removed only redundant or premature test scaffolding.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ releases may include breaking changes.
[#1446], [#1513], [#1521], [#1548], [#1567], [#1569], [#1570], [#1572],
[#1580], [#1620], [#1624], [#1626], [#1648], [#1710], [#1751], [#1755],
[#1787], [#1815], [#1823], [#1933], [#1978], [#1979], [#2007], [#2026],
[#2030], [#2066], [#2217]) ([**@burgholzer**], [**@denialhaag**],
[#2030], [#2066], [#2217], [#2340]) ([**@burgholzer**], [**@denialhaag**],
[**@simon1hofmann**], [**@li-mingbao**], [**@DRovara**],
[**@MatthiasReumann**])
- ✨ Add OpenQASM import and export to the MQT Compiler Collection, including
Expand Down Expand Up @@ -928,6 +928,7 @@ for previous changelogs._
[#2368]: https://github.com/munich-quantum-toolkit/core/pull/2368
[#2358]: https://github.com/munich-quantum-toolkit/core/pull/2358
[#2349]: https://github.com/munich-quantum-toolkit/core/pull/2349
[#2340]: https://github.com/munich-quantum-toolkit/core/pull/2340
[#2339]: https://github.com/munich-quantum-toolkit/core/pull/2339
[#2337]: https://github.com/munich-quantum-toolkit/core/pull/2337
[#2336]: https://github.com/munich-quantum-toolkit/core/pull/2336
Expand Down
3 changes: 3 additions & 0 deletions mlir/include/mlir/Support/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ void registerMQTCompilerPasses();
/// Populate the default QCO optimization pipeline.
void populateDefaultQCOOptimizationPipeline(mlir::OpPassManager& pm);

/// Prepare reusable QC functions and modifiers for QIR conversion.
void populateQIRPreparationPipeline(mlir::OpPassManager& pm);

/// Populate the qubit reuse pipeline including its preparation passes.
void populateQubitReusePipeline(mlir::OpPassManager& pm);

Expand Down
2 changes: 2 additions & 0 deletions mlir/lib/Compiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ add_mlir_library(
MLIRCBitDialect
MLIRControlFlowDialect
MLIRFuncDialect
MLIRFuncInlinerExtension
MLIRIR
MLIRJeff
MLIRLLVMDialect
MLIRLLVMIRTransforms
MLIRLLVMToLLVMIRTranslation
MLIRMathDialect
MLIRMemRefDialect
Expand Down
12 changes: 11 additions & 1 deletion mlir/lib/Compiler/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <mlir/Pass/PassManager.h>
#include <mlir/Support/LogicalResult.h>
#include <mlir/Target/LLVMIR/ModuleTranslation.h>
#include <mlir/Transforms/Passes.h>

#include <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -122,7 +123,7 @@ std::optional<QIRProgram> QCProgram::intoQIR(QIRProfile profile) && {
if (failed(runPasses(
mod(),
[profile](OpPassManager& pm) {
pm.addPass(mqt::createUnrollModifiers());
populateQIRPreparationPipeline(pm);
if (profile == QIRProfile::Adaptive) {
pm.addPass(createQCToQIRAdaptive());
} else {
Expand Down Expand Up @@ -481,6 +482,15 @@ runDefaultPipeline(CompilerInput&& program, ProgramFormat output,
return CompilerProgram(std::move(*qco));
}

if ((output == ProgramFormat::QIRBase ||
output == ProgramFormat::QIRAdaptive) &&
failed(runQCOTransformPasses(
qco->module(),
[](OpPassManager& pm) { pm.addPass(createInlinerPass()); },
"failed to inline QCO calls", enableTiming, enableStatistics))) {
return std::nullopt;
}

if (target != nullptr) {
if (!qco->compileForTarget(*target, enableTiming, enableStatistics)) {
return std::nullopt;
Expand Down
16 changes: 15 additions & 1 deletion mlir/lib/Compiler/Programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
#include <llvm/Support/raw_ostream.h>
#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/Dialect/ControlFlow/IR/ControlFlow.h>
#include <mlir/Dialect/Func/Extensions/InlinerExtension.h>
#include <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/Dialect/LLVMIR/LLVMDialect.h>
#include <mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h>
#include <mlir/Dialect/Math/IR/Math.h>
#include <mlir/Dialect/MemRef/IR/MemRef.h>
#include <mlir/Dialect/SCF/IR/SCF.h>
Expand Down Expand Up @@ -61,6 +63,13 @@

namespace mlir {

static void ensureInlinerExtensions(MLIRContext* context) {
DialectRegistry registry;
func::registerInlinerExtension(registry);
LLVM::registerInlinerInterface(registry);
context->appendDialectRegistry(registry);
}

std::shared_ptr<MLIRContext> createCompilerContext() {
DialectRegistry registry;
registry.insert<cbit::CBitDialect, mqt::MQTDialect, qc::QCDialect,
Expand All @@ -72,6 +81,7 @@ std::shared_ptr<MLIRContext> createCompilerContext() {
registerLLVMDialectTranslation(registry);

auto context = std::make_shared<MLIRContext>(registry);
ensureInlinerExtensions(context.get());
context->loadAllAvailableDialects();
return context;
}
Expand Down Expand Up @@ -143,7 +153,11 @@ parseTypedProgram(Parse&& parse) {
// Program
//===----------------------------------------------------------------------===//

Program::Program(Storage storage) : storage_(std::move(storage)) {}
Program::Program(Storage storage) : storage_(std::move(storage)) {
if (storage_.context) {
ensureInlinerExtensions(storage_.context.get());
}
}

bool Program::isValid() const noexcept {
return static_cast<bool>(storage_.mod);
Expand Down
7 changes: 7 additions & 0 deletions mlir/lib/Support/Passes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ void populateDefaultQCOOptimizationPipeline(OpPassManager& pm) {
pm.addPass(qco::createMergeSingleQubitRotationGates());
}

void populateQIRPreparationPipeline(OpPassManager& pm) {
pm.addPass(createInlinerPass());
pm.addPass(mqt::createNormalizeGlobalPhases());
pm.addPass(mqt::createUnrollModifiers());
pm.addPass(createCanonicalizerPass());
}

void populateQubitReusePipeline(OpPassManager& pm) {
pm.addPass(qco::createMeasurementLifting());
pm.addPass(qco::createReplaceClassicalControls());
Expand Down
13 changes: 11 additions & 2 deletions mlir/tools/mqt-cc/mqt-cc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
#include <mlir/Bytecode/BytecodeWriter.h>
#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/Dialect/ControlFlow/IR/ControlFlow.h>
#include <mlir/Dialect/Func/Extensions/InlinerExtension.h>
#include <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/Dialect/LLVMIR/LLVMDialect.h>
#include <mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h>
#include <mlir/Dialect/Math/IR/Math.h>
#include <mlir/Dialect/MemRef/IR/MemRef.h>
#include <mlir/Dialect/SCF/IR/SCF.h>
Expand All @@ -63,6 +65,7 @@
#include <mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h>
#include <mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h>
#include <mlir/Target/LLVMIR/Export.h>
#include <mlir/Transforms/Passes.h>

#include <cstdint>
#include <cstdlib>
Expand Down Expand Up @@ -461,6 +464,8 @@ static int runCompiler(int argc, char** argv) {
tensor::TensorDialect, jeff::JeffDialect>();
registerBuiltinDialectTranslation(registry);
registerLLVMDialectTranslation(registry);
func::registerInlinerExtension(registry);
LLVM::registerInlinerInterface(registry);

MLIRContext context(registry);
context.loadAllAvailableDialects();
Expand Down Expand Up @@ -534,6 +539,10 @@ static int runCompiler(int argc, char** argv) {
*parsedOutputFormat != OutputFormat::QCImport &&
*parsedOutputFormat != OutputFormat::QCO;
if (requiresPostQcoPasses && failed(runPasses([&](OpPassManager& pm) {
if (*parsedOutputFormat == OutputFormat::QIRBase ||
*parsedOutputFormat == OutputFormat::QIRAdaptive) {
pm.addPass(createInlinerPass());
}
if (compilerTarget) {
populateTargetCompilationPipeline(pm, *compilerTarget);
return success();
Expand Down Expand Up @@ -583,7 +592,7 @@ static int runCompiler(int argc, char** argv) {

if (*parsedOutputFormat == OutputFormat::QIRBase &&
failed(runPasses([](OpPassManager& pm) {
pm.addPass(mqt::createUnrollModifiers());
populateQIRPreparationPipeline(pm);
pm.addPass(createQCToQIRBase());
populateQIRCleanupPipeline(pm, false);
return success();
Expand All @@ -593,7 +602,7 @@ static int runCompiler(int argc, char** argv) {

if (*parsedOutputFormat == OutputFormat::QIRAdaptive &&
failed(runPasses([](OpPassManager& pm) {
pm.addPass(mqt::createUnrollModifiers());
populateQIRPreparationPipeline(pm);
pm.addPass(createQCToQIRAdaptive());
populateQIRCleanupPipeline(pm, true);
return success();
Expand Down
Loading
Loading