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
132 changes: 132 additions & 0 deletions .agent/plans/jeff-reusable-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Preserve reusable functions in jeff programs

This ExecPlan follows `.agent/PLANS.md`. Its progress, discoveries, decisions,
and results must stay current. Run commands from the repository root.

## Purpose

Preserve function definitions and calls when exchanging QCO programs with jeff.
Repeated applications of a parameterized circuit should share one helper body,
including after binary serialization. Expand a call only when a quantum modifier
requires it: jeff supports ordinary calls, but its call instruction has no
control, inverse, or power modifier.

## Progress

- [x] (2026-09-04) Rebase onto current `main` and inspect the updated compiler
structure and pinned jeff reader.
- [x] (2026-09-04) Preserve native function definitions and calls in both
directions and correct entry-point indexing.
- [x] (2026-09-04) Expand calls only under quantum modifiers and normalize
exposed global phases.
- [x] (2026-09-04) Remove call-graph ordering and recursion restrictions now
that the pinned reader registers all function signatures before bodies.
- [x] (2026-09-04) Add binary-roundtrip, metadata, mutable-register, and
controlled-phase regressions.
- [x] (2026-09-04) Build and pass all 151 jeff conversion tests and all 158
compiler tests against the dependency revision pinned by current `main`.
- [ ] Run repository lint on the final stack and record the result here.

## Surprises & Discoveries

The jeff schema represents complete programs as a function table and a
designated entry function. It supports function calls directly. The pinned
jeff-mlir dependency serializes and reads `func.func` and `func.call`; no new
schema or private metadata is needed.

The entry-point attribute indexes the function table, not the string table. The
old conversions confused those tables. This can select a wrong function when a
custom gate adds a string or when multiple functions are present.

The pinned reader now registers every function signature before reading bodies.
Forward references and recursion therefore need no exporter-specific ordering or
analysis.

## Decision Log

Use MLIR's function, call, and return type-conversion patterns. Add only the
conversion from `qco.call` to `func.call`. Strip `mqt.unitary` on export because
the verifier describes QC/QCO bodies, not jeff bodies. Do not infer this marker
on import. A complete imported program has one public entry point; make its
other functions private so downstream inlining can remove unused helpers.

Use `inlineCall` only under QC/QCO modifiers, through the existing
`unroll-modifiers` pass. Its native inliner callback collects newly exposed
calls without rescanning the whole program or removing unrelated symbols.
Register promised inliner interfaces in the pass's dependent-dialect registry.
Normalize newly exposed global phases before distributing modifiers.

Preserve live function signatures in QC/QCO cleanup. Omit native dead-value
removal there because it erases unused private-function arguments, including
required borrowed qubits. Keep canonicalization, CSE, and register shrinking.

Keep source function order. Reject declarations and multiple outer blocks
because the serializer requires defined single-block functions. Reject mutable
classical-register helper arguments: their reference semantics cannot be
represented by passing a jeff array value without an explicit ABI.

## Scope and implementation

`mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp` owns helper signature conversion,
native calls, and serialized module metadata.
`mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp` owns the inverse type conversion,
entry-point lookup, and private helper visibility. Keep these changes in the
existing conversion libraries.

`mlir/lib/Dialect/MQT/Transforms/UnrollModifiers.cpp` owns call expansion under
modifiers. QC and QCO dialect inliner interfaces supply the legality rules;
verified unitary functions are private, defined, nonrecursive, and single-block.
Do not add a full-program flattening pass or change caller-owned contexts in the
compiler API. Keep ordinary calls and helper definitions intact.

The compiler regression in `mlir/unittests/Compiler/test_compiler_pipeline.cpp`
exports a parameterized helper used twice, serializes it, imports it, and
converts it to QC. The test checks function count, call count, gate body,
visibility, and entry-point identity. Include a custom gate so function and
string indexes differ.
`mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp` owns the
controlled-helper phase regression and metadata diagnostics, including a forward
reference that exercises the corrected reader.

## Milestones and validation

First build the conversion and compiler tests:

cmake --preset release
cmake --build --preset release --target mqt-core-mlir-unittest-jeff-round-trip mqt-core-mlir-unittests-compiler -j4

Run focused tests, then their complete binaries:

build/release/mlir/unittests/Compiler/mqt-core-mlir-unittests-compiler --gtest_filter='CompilerPipelineTest.*Jeff*'
build/release/mlir/unittests/Conversion/JeffRoundTrip/mqt-core-mlir-unittest-jeff-round-trip
build/release/mlir/unittests/Compiler/mqt-core-mlir-unittests-compiler

Both native calls must survive the binary roundtrip with one shared helper body.
The controlled helper must retain its relative phase and controlled gate. Input
and successful output must verify. QC conversion must remove positional
pass-through quantum results while retaining helper calls.

Finish with:

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

Keep target and QIR integration tests in the subsequent stack layers. Those
changes own flattening required by their output formats and must exercise
imported private jeff helpers.

## Recovery and coordination

Builds and tests are repeatable. Preserve unrelated work and do not modify
another task's worktree. Do not change the external jeff-mlir dependency without
coordination. This plan does not authorize remote actions. Inspect the complete
commit message, sign commits, and verify signatures before publication.

## Outcomes & Retrospective

The focused build and tests pass. The rebased design relies on the format's
native function table and corrected dependency reader, so no call graph,
function reordering, recursion policy, or private metadata is needed. Ordinary
calls remain reusable; only calls inside unsupported quantum modifiers are
expanded. An independent jeff/MLIR review removed one unnecessary whole-module
phase-normalization run when no candidate call can be inlined.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ releases may include breaking changes.
collection ([#2031], [#2133], [#2140], [#2150], [#2175], [#2176], [#2178])
([**@burgholzer**], [**@simon1hofmann**])
- ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637],
[#1676], [#1706], [#1776], [#1836], [#1934], [#2000], [#2018], [#2105])
([**@denialhaag**], [**@burgholzer**])
[#1676], [#1706], [#1776], [#1836], [#1934], [#2000], [#2018], [#2105],
[#2339]) ([**@denialhaag**], [**@burgholzer**])
- ✨ Add QIR generation support to the MQT Compiler Collection ([#1264],
[#1446], [#1513], [#1521], [#1548], [#1567], [#1569], [#1570], [#1572],
[#1580], [#1620], [#1624], [#1626], [#1648], [#1710], [#1751], [#1755],
Expand Down Expand Up @@ -886,6 +886,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
[#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
[#2335]: https://github.com/munich-quantum-toolkit/core/pull/2335
Expand Down
11 changes: 8 additions & 3 deletions mlir/include/mlir/Conversion/JeffToQCO/JeffToQCO.td
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ def JeffToQCO : Pass<"jeff-to-qco", "mlir::ModuleOp"> {

- Only specific `CustomOp`s are currently supported
- Only specific `PPROp`s are currently supported
- Support for multiple functions is currently limited
- Mutable classical-register arguments in helper functions are not supported

Function definitions and calls are preserved. Only the designated entry
point remains public; helper functions become private. Calls remain generic
`func.call` operations because jeff does not specify function unitarity.
}];

let dependentDialects = ["mlir::arith::ArithDialect",
"mlir::cbit::CBitDialect", "mlir::math::MathDialect",
"mlir::mqt::MQTDialect", "mlir::scf::SCFDialect",
"mlir::func::FuncDialect", "mlir::cbit::CBitDialect",
"mlir::math::MathDialect", "mlir::mqt::MQTDialect",
"mlir::scf::SCFDialect",
"mlir::tensor::TensorDialect",
"mlir::qco::QCODialect",
"mlir::qtensor::QTensorDialect",
Expand Down
6 changes: 5 additions & 1 deletion mlir/include/mlir/Conversion/QCOToJeff/QCOToJeff.td
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ def QCOToJeff : Pass<"qco-to-jeff", "mlir::ModuleOp"> {
This pass converts all operations from the QCO dialect to their equivalent operations in the `jeff` dialect.
It ensures that the returned module is a valid `jeff` module that can be serialized.

Defined single-block functions and their calls are preserved. Mutable
classical-register arguments in helpers are not supported. Unitary calls
inside quantum modifiers must be expanded with `unroll-modifiers` first.

Note that this pass is still in development as QCO and `jeff` do not have full feature parity yet.

`qco.static` is currently converted to `jeff.qubit_alloc` because `jeff` does not yet represent static (index-based) qubits.
As the index is not preserved in `jeff`, it is not possible to round-tripping static qubits.
}];

let dependentDialects = ["mlir::jeff::JeffDialect",
let dependentDialects = ["mlir::func::FuncDialect", "mlir::jeff::JeffDialect",
"mlir::arith::ArithDialect"];
}
6 changes: 5 additions & 1 deletion mlir/include/mlir/Dialect/MQT/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def UnrollModifiers : Pass<"unroll-modifiers", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::qc::QCDialect", "mlir::qco::QCODialect"];
let summary = "Unroll multi-operation modifiers into single-operation ones";
let description = [{
Expands unitary function calls inside quantum modifiers and normalizes
exposed global phases before splitting modifiers. Calls outside modifiers
remain reusable. Functions marked `no_inline` remain calls.

Splits `ctrl` and `inv` modifiers whose body holds more than one unitary
operation into a sequence of modifiers that each hold a single operation.
For `inv`, the order of the operations is reversed. Classical operations of
Expand All @@ -51,7 +55,7 @@ def UnrollModifiers : Pass<"unroll-modifiers", "mlir::ModuleOp"> {
them. Modifiers nested in a `pow` body are unrolled either way.

If a modifier cannot be unrolled, the modifier is left untouched. Such
modifiers are skipped silently; the pass never fails.
modifiers are skipped silently.
}];
}

Expand Down
68 changes: 28 additions & 40 deletions mlir/include/mlir/Support/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ class OpPassManager;
class PassManager;
} // namespace mlir

/**
* @brief Populate the pass manager and run it on the module.
*/
/// Populate the pass manager and run it on the module.
mlir::LogicalResult runWithPassManager(
mlir::ModuleOp module,
mlir::ModuleOp moduleOp,
mlir::function_ref<void(mlir::OpPassManager&)> populatePasses,
mlir::StringRef errorMessage);

Expand All @@ -46,58 +44,48 @@ void populateDecomposeMultiControlledPipeline(mlir::OpPassManager& pm,

/// Parse and run a module-level MLIR textual pass pipeline.
[[nodiscard]] mlir::LogicalResult
runPassPipeline(mlir::ModuleOp module, mlir::StringRef pipeline,
runPassPipeline(mlir::ModuleOp moduleOp, mlir::StringRef pipeline,
bool enableTiming = false, bool enableStatistics = false);

/**
* @brief Populate a QC-oriented cleanup pipeline on the given pass manager.
* @details Adds generic cleanup and QC qubit-register shrinking.
*/
/// Populate a QC-oriented cleanup pipeline on the given pass manager.
///
/// Adds generic cleanup and QC qubit-register shrinking while
/// preserving live function signatures.
void populateQCCleanupPipeline(mlir::OpPassManager& pm);

/// Run QC cleanup that preserves defined values on every syntactic loop edge.
/// Source formats cannot represent the poison backedge values introduced by
/// RemoveDeadValues, even when those edges are unreachable.
void populateQCExportPipeline(mlir::OpPassManager& pm);

/**
* @brief Populate a QCO-oriented cleanup pipeline on the given pass manager.
* @details Adds generic cleanup and qtensor shrink-to-fit.
*/
/// Populate a QCO-oriented cleanup pipeline on the given pass manager.
///
/// Adds generic cleanup and qtensor shrink-to-fit while
/// preserving live function signatures.
void populateQCOCleanupPipeline(mlir::OpPassManager& pm);

/**
* @brief Populate a QIR-oriented cleanup pipeline on the given pass manager.
* @details Adds generic cleanup and QIR-specific simplifications. Updates the
* meta data accordingly.
*/
/// Populate a QIR-oriented cleanup pipeline on the given pass manager.
///
/// Adds generic cleanup and QIR-specific simplifications. Updates the
/// metadata accordingly.
void populateQIRCleanupPipeline(mlir::OpPassManager& pm, bool useAdaptive);

/**
* @brief Populate a `jeff`-oriented cleanup pipeline on the given pass manager.
* @details Adds generic cleanup and dead-value removal. This matches the QCO
* cleanup minus the QTensor-specific shrink pass, as QTensor operations no
* longer exist once lowered into the `jeff` dialect.
*/
/// Populate a `jeff`-oriented cleanup pipeline on the given pass manager.
///
/// Adds generic cleanup and dead-value removal after lowering to jeff.
void populateJeffCleanupPipeline(mlir::OpPassManager& pm);

/**
* @brief Run the QC-oriented cleanup pipeline on a module.
*/
[[nodiscard]] mlir::LogicalResult runQCCleanupPipeline(mlir::ModuleOp module);
/// Run the QC-oriented cleanup pipeline on a module.
[[nodiscard]] mlir::LogicalResult runQCCleanupPipeline(mlir::ModuleOp moduleOp);

/**
* @brief Run the QCO-oriented cleanup pipeline on a module.
*/
[[nodiscard]] mlir::LogicalResult runQCOCleanupPipeline(mlir::ModuleOp module);
/// Run the QCO-oriented cleanup pipeline on a module.
[[nodiscard]] mlir::LogicalResult
runQCOCleanupPipeline(mlir::ModuleOp moduleOp);

/**
* @brief Run the QIR-oriented cleanup pipeline on a module.
*/
[[nodiscard]] mlir::LogicalResult runQIRCleanupPipeline(mlir::ModuleOp module,
/// Run the QIR-oriented cleanup pipeline on a module.
[[nodiscard]] mlir::LogicalResult runQIRCleanupPipeline(mlir::ModuleOp moduleOp,
bool useAdaptive);

/**
* @brief Run the `jeff`-oriented cleanup pipeline on a module.
*/
[[nodiscard]] mlir::LogicalResult runJeffCleanupPipeline(mlir::ModuleOp module);
/// Run the `jeff`-oriented cleanup pipeline on a module.
[[nodiscard]] mlir::LogicalResult
runJeffCleanupPipeline(mlir::ModuleOp moduleOp);
1 change: 1 addition & 0 deletions mlir/lib/Conversion/JeffToQCO/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_mlir_conversion_library(
LINK_LIBS
MLIRJeff
MLIRJeffToNative
MLIRFuncTransforms
MLIRCBitDialect
MLIRMQTDialect
MLIRQCODialect
Expand Down
Loading
Loading